blob: 5a84cbbe05e920cf57c968bd4e8796bc1d8bbd59 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.javafx2.project;
import java.io.*;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.MessageFormat;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.JavaClassPathConstants;
import org.netbeans.api.java.platform.JavaPlatform;
import org.netbeans.api.java.platform.JavaPlatformManager;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.source.*;
import org.netbeans.api.java.source.ClassIndex.SearchKind;
import org.netbeans.api.java.source.ClassIndex.SearchScope;
import org.netbeans.api.project.*;
import org.netbeans.api.project.ant.AntBuildExtender;
import org.netbeans.api.project.ui.OpenProjects;
import org.netbeans.modules.extbrowser.ExtWebBrowser;
import org.netbeans.modules.java.api.common.project.ProjectProperties;
import org.netbeans.modules.java.j2seproject.api.J2SEProjectPlatform;
import org.netbeans.modules.java.j2seproject.api.J2SEPropertyEvaluator;
import org.netbeans.modules.javafx2.platform.api.JavaFXPlatformUtils;
import org.netbeans.modules.javafx2.platform.api.JavaFxRuntimeInclusion;
import org.netbeans.modules.javafx2.project.JavaFXProjectWizardIterator.WizardType;
import org.netbeans.modules.javafx2.project.ui.JSEApplicationClassChooser;
import org.netbeans.spi.project.ProjectIconAnnotator;
import org.netbeans.spi.project.support.ant.AntProjectHelper;
import org.netbeans.spi.project.support.ant.EditableProperties;
import org.netbeans.spi.project.support.ant.GeneratedFilesHelper;
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
import org.netbeans.spi.project.support.ant.PropertyUtils;
import org.openide.awt.Notification;
import org.openide.awt.NotificationDisplayer;
import org.openide.cookies.CloseCookie;
import org.openide.execution.NbProcessDescriptor;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObject;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.Mutex;
import org.openide.util.MutexException;
import org.openide.util.NbBundle;
import org.openide.util.Parameters;
import org.openide.xml.XMLUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Utility class for JavaFX 2.0+ Project
*
* @author Petr Somol
*/
public final class JFXProjectUtils {
private static Set<SearchKind> kinds = new HashSet<SearchKind>(Arrays.asList(SearchKind.IMPLEMENTORS));
private static Set<SearchScope> scopes = new HashSet<SearchScope>(Arrays.asList(SearchScope.SOURCE));
private static final String JFX_BUILD_TEMPLATE = "Templates/JFX/jfx-impl.xml"; //NOI18N
private static final String CURRENT_EXTENSION = "jfx3"; //NOI18N
private static final String[] OLD_EXTENSIONS = new String[] {"jfx", "jfx2"}; // NOI18N
private static final String NBPROJECT = "nbproject"; // NOI18N
private static final String JFX_BUILD_IMPL_NAME = "jfx-impl"; // NOI18N
private static final String JFX_BUILD_IMPL_PATH = NBPROJECT + "/" + JFX_BUILD_IMPL_NAME + ".xml"; //NOI18N
private static volatile String currentJfxImplCRCCache;
private static final String TRUE = "true"; //NOI18N
private static final String FALSE = "false"; //NOI18N
// from J2SEDeployProperties
private static final String J2SEDEPLOY_EXTENSION = "j2sedeploy"; //NOI18N
private static final String EXTENSION_BUILD_SCRIPT_PATH = "nbproject/build-native.xml"; //NOI18N
//Important path types for Java model
private static final String[] JAVA_PATHS = {
ClassPath.BOOT, ClassPath.COMPILE, ClassPath.SOURCE,
JavaClassPathConstants.MODULE_BOOT_PATH, JavaClassPathConstants.MODULE_COMPILE_PATH,
JavaClassPathConstants.MODULE_CLASS_PATH, /*JavaClassPathConstants.MODULE_SOURCE_PATH*/
};
// two deprecated properties, to be auto-cleaned from project.properties if present
@Deprecated
public static final String PROPERTY_JAVAFX_RUNTIME = "javafx.runtime"; // NOI18N
@Deprecated
public static final String PROPERTY_JAVAFX_SDK = "javafx.sdk"; // NOI18N
private static final Logger LOGGER = Logger.getLogger("javafx"); // NOI18N
/**
* Return current name (version) of build-impl.xml "extension spi"
* where the spi consists of target dependency points between build-impl
* and jfx-impl.
* @return name as string
*/
public static String getCurrentExtensionName() {
return CURRENT_EXTENSION;
}
/**
* Return list of old names (versions) of build-impl.xml "extension spis".
* Used to search for spi version when jfx-impl and build-impl do not match
* and correct update needs to be determined.
* @return names as strings
*/
public static Iterable<? extends String> getOldExtensionNames() {
return Arrays.asList(OLD_EXTENSIONS);
}
/**
* Returns list of JavaFX 2.0 JavaScript callback entries.
* In future should read the list from the current platform
* (directly from FX SDK or Ant taks).
* Current list taken from
* http://javaweb.us.oracle.com/~in81039/new-dt/js-api/Callbacks.html
*
* @param IDE java platform name
* @return callback entries
*/
public static Map<String,List<String>/*|null*/> getJSCallbacks(String platformName) {
final String[][] c = {
{"onDeployError", "app", "mismatchEvent"}, // NOI18N
{"onGetNoPluginMessage", "app"}, // NOI18N
{"onGetSplash", "app"}, // NOI18N
{"onInstallFinished", "placeholder", "component", "status", "relaunchNeeded"}, // NOI18N
{"onInstallNeeded", "app", "platform", "cb", "isAutoinstall", "needRelaunch", "launchFunc"}, // NOI18N
{"onInstallStarted", "placeholder", "component", "isAuto", "restartNeeded"}, // NOI18N
{"onJavascriptReady", "id"}, // NOI18N
{"onRuntimeError", "id", "code"} // NOI18N
};
Map<String,List<String>/*|null*/> m = new LinkedHashMap<String,List<String>/*|null*/>();
for(int i = 0; i < c.length; i++) {
String[] s = c[i];
assert s.length > 0;
List<String> l = null;
if(s.length > 1) {
l = new ArrayList<String>();
for(int j = 1; j < s.length; j++) {
l.add(s[j]);
}
}
m.put(s[0], l);
}
return m;
}
/**
* Returns all classpaths relevant for given project. To be used in
* main class searches.
*
* @param project
* @return map of classpaths of all project files
*/
public static Map<FileObject,Map<String,ClassPath>> getClassPathMap(@NonNull Project project) {
Sources sources = ProjectUtils.getSources(project);
SourceGroup[] srcGroups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
final Map<FileObject,Map<String,ClassPath>> classpathMap = new HashMap<>();
final BitSet requiredPaths = new BitSet(3);
requiredPaths.set(0, 3, true);
for (SourceGroup srcGroup : srcGroups) {
FileObject srcRoot = srcGroup.getRootFolder();
final Map<String, ClassPath> sgCps = new HashMap<>();
final BitSet seen = new BitSet(JAVA_PATHS.length);
for (int i = 0; i < JAVA_PATHS.length; i++) {
final ClassPath cp = ClassPath.getClassPath(srcRoot, JAVA_PATHS[i]);
if (cp != null) {
sgCps.put(JAVA_PATHS[i], cp);
seen.set(i);
}
}
seen.and(requiredPaths);
if (seen.cardinality() == 3) {
classpathMap.put(srcRoot, sgCps);
}
}
return classpathMap;
}
/**
* Returns source roots of a project
* @param project
*
* @return set of fileobjects representing the source roots
*/
public static Set<FileObject> getSourceRoots(@NonNull Project project) {
Sources sources = ProjectUtils.getSources(project);
SourceGroup[] srcGroups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
final Set<FileObject> sourceRoots = new HashSet<FileObject>();
for (SourceGroup srcGroup : srcGroups) {
sourceRoots.add(srcGroup.getRootFolder());
}
return sourceRoots;
}
/**
* Returns set of names of main classes in the given project
*
* @param project
* @return set of class names
*/
public static Set<String> getMainClassNames(@NonNull Project project) {
final Set<String> mainClassNames = new HashSet<String>();
FileObject sourceRoots[] = getSourceRoots(project).toArray(new FileObject[0]);
for (ElementHandle<TypeElement> elemHandle : SourceUtils.getMainClasses(sourceRoots)) {
mainClassNames.add(elemHandle.getQualifiedName());
}
return mainClassNames;
}
/**
* Returns set of names of classes of the classType type.
*
* @param classpathMap map of classpaths of all project files
* @param classType return only classes of this type
* @return set of class names
*/
public static Set<String> getAppClassNames(@NonNull Collection<? extends FileObject> roots, final @NonNull String classType) {
final Set<String> appClassNames = new HashSet<>();
for (FileObject fo : roots) {
final ClasspathInfo cpInfo = ClasspathInfo.create(fo);
final JavaSource js = JavaSource.create(cpInfo);
if (js != null) {
try {
js.runUserActionTask(new Task<CompilationController>() {
@Override
public void run(CompilationController controller) throws Exception {
final ClassIndex classIndex = cpInfo.getClassIndex();
final Elements elems = controller.getElements();
TypeElement fxAppElement = elems.getTypeElement(classType);
ElementHandle<TypeElement> appHandle = ElementHandle.create(fxAppElement);
Set<ElementHandle<TypeElement>> appHandles = classIndex.getElements(appHandle, kinds, scopes);
for (ElementHandle<TypeElement> elemHandle : appHandles) {
appClassNames.add(elemHandle.getQualifiedName());
}
}
}, true);
} catch (Exception e) {
}
}
}
return appClassNames;
}
/** Finds available FX Preloader classes in given JAR files.
* Looks for classes specified in the JAR manifest only.
*
* @param jarFile FileObject representing an existing JAR file
* @param classType return only classes of this type
* @return set of class names
*/
public static Set<String> getAppClassNamesInJar(@NonNull FileObject jarFile, final String classType, final String fxrtJarPath) {
final File jarF = FileUtil.toFile(jarFile);
if (jarF == null) {
return null;
}
boolean jfxrtExists = false;
List<URL> toLoad = new ArrayList<URL>();
try {
assert jarF.exists();
toLoad.add(jarF.toURI().toURL());
final File jfxrt = new File(fxrtJarPath);
if(jfxrt.exists()) {
jfxrtExists = true;
}
toLoad.add(jfxrt.toURI().toURL());
} catch (MalformedURLException ex) {
return null;
}
URLClassLoader clazzLoader = URLClassLoader.newInstance(toLoad.toArray(new URL[0]));
final Set<String> appClassNames = new HashSet<String>();
JarFile jf;
try {
jf = new JarFile(jarF);
} catch (IOException x) {
return null;
}
Enumeration<? extends JarEntry> entries = jf.entries();
if (entries == null) {
return null;
}
while(entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
// Relative path of file into the jar
String classFileName = entry.getName();
if(!classFileName.endsWith(".class")) { // NOI18N
continue;
}
if(classFileName.contains("$")) { // NOI18N
continue;
}
// Complete class name
String className = classFileName.replace(".class", "").replace('\\', '/').replace('/', '.'); // NOI18N
if(clazzLoader != null && jfxrtExists) {
// Load class definition from JVM
Class<?> clazz;
try {
clazz = Class.forName(className, true, clazzLoader);
Type t = clazz.getGenericSuperclass();
if(t.toString().contains(classType)) {
if (className.startsWith(".")) { // NOI18N
className = className.substring(1);
}
appClassNames.add(className);
}
} catch (ClassNotFoundException ex) {
return null;
}
} else {
if (className.startsWith(".")) { // NOI18N
className = className.substring(1);
}
appClassNames.add(className);
}
}
return appClassNames;
}
/**
* Checks if the JFX support is enabled for given project
* @param prj the project to check
* @return true if project supports JFX
*/
public static boolean isFXProject(@NonNull final Project prj) {
final J2SEPropertyEvaluator ep = prj.getLookup().lookup(J2SEPropertyEvaluator.class);
if (ep == null) {
return false;
}
return JFXProjectProperties.isTrue(ep.evaluator().getProperty(JFXProjectProperties.JAVAFX_ENABLED));
}
/**
* Checks if the project is a Swing project with JFX support enabled
* @param prj the project to check
* @return true if project supports FX in Swing
*/
public static boolean isFXinSwingProject(@NonNull final Project prj) {
final J2SEPropertyEvaluator ep = prj.getLookup().lookup(J2SEPropertyEvaluator.class);
if (ep == null) {
return false;
}
return JFXProjectProperties.isTrue(ep.evaluator().getProperty(JFXProjectProperties.JAVAFX_ENABLED))
&& JFXProjectProperties.isTrue(ep.evaluator().getProperty(JFXProjectProperties.JAVAFX_SWING));
}
/**
* Checks if the project is a JavaFX preloader project. Note that in pre-7.2 NB
* preloader type had been used for fx-in-swing as workaround, hence the logic below
* @param prj the project to check
* @return true if project is JavaFX preloader
*/
public static boolean isFXPreloaderProject(@NonNull final Project prj) {
final J2SEPropertyEvaluator ep = prj.getLookup().lookup(J2SEPropertyEvaluator.class);
if (ep == null) {
return false;
}
return JFXProjectProperties.isTrue(ep.evaluator().getProperty(JFXProjectProperties.JAVAFX_ENABLED))
&& JFXProjectProperties.isTrue(ep.evaluator().getProperty(JFXProjectProperties.JAVAFX_PRELOADER))
&& !JFXProjectProperties.isTrue(ep.evaluator().getProperty(JFXProjectProperties.JAVAFX_SWING));
}
/**
* Checks if the project uses Maven build system
*
* @param prj the project to check
* @return true if is Maven project
*/
public static boolean isMavenProject(@NonNull final Project prj) {
FileObject fo = prj.getProjectDirectory();
if (fo == null || !fo.isValid()) {
return false;
}
fo = fo.getFileObject("pom.xml"); //NOI18N
return fo != null;
}
/**
* Checks if the project uses Gradle build system
*
* @param prj the project to check
* @return true if is Gradle project
*/
public static boolean isGradleProject(@NonNull final Project prj) {
FileObject fo = prj.getProjectDirectory();
if (fo == null || !fo.isValid()) {
return false;
}
fo = fo.getFileObject("build.gradle"); //NOI18N
return fo != null;
}
public static boolean isMavenFXProject(@NonNull final Project prj) {
if (isMavenProject(prj)) {
try {
FileObject pomXml = prj.getProjectDirectory().getFileObject("pom.xml"); //NOI18N
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(FileUtil.toFile(pomXml));
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
XPathExpression exprJfxrt = xpath.compile("//bootclasspath[contains(text(),'jfxrt')]"); //NOI18N
XPathExpression exprFxPackager = xpath.compile("//executable[contains(text(),'javafxpackager')]"); //NOI18N
XPathExpression exprPackager = xpath.compile("//executable[contains(text(),'javapackager')]"); //NOI18N
boolean jfxrt = (Boolean) exprJfxrt.evaluate(doc, XPathConstants.BOOLEAN);
boolean packager = (Boolean) exprPackager.evaluate(doc, XPathConstants.BOOLEAN);
boolean fxPackager = (Boolean) exprFxPackager.evaluate(doc, XPathConstants.BOOLEAN);
return jfxrt && (packager || fxPackager);
} catch (XPathExpressionException | ParserConfigurationException | SAXException | IOException ex) {
LOGGER.log(Level.INFO, "Error while parsing pom.xml.", ex); //NOI18N
return false;
}
}
return false;
}
/**
* Checks what Run model is selected in current configuration of JFX Run Project Property panel
* @param prj the project to check
* @return string value of JFXProjectProperties.RunAsType type or null meaning JFXProjectProperties.RunAsType.STANDALONE
*/
public static String getFXProjectRunAs(@NonNull final Project prj) {
final J2SEPropertyEvaluator ep = prj.getLookup().lookup(J2SEPropertyEvaluator.class);
if (ep == null) {
return null;
}
return ep.evaluator().getProperty(JFXProjectProperties.RUN_AS);
}
/**
* Finds the relative path to targetFO from sourceFO.
* Unlike FileUtil.getRelativePath() does not require targetFO to be within sourceFO sub-tree
* Returns null if there is no shared parent directory except root.
*
* @param sourceFO file/dir to which the relative path will be related
* @param targetFO file whose location will be determined with respect to sourceFO
* @return string relative path leading from sourceFO to targetFO
*/
public static String getRelativePath(@NonNull final FileObject sourceFO, @NonNull final FileObject targetFO) {
String path = ""; //NOI18N
FileObject src = sourceFO;
FileObject tgt = targetFO;
String targetName = null;
if(!src.isFolder()) {
src = src.getParent();
}
if(!tgt.isFolder()) {
targetName = tgt.getNameExt();
tgt = tgt.getParent();
}
LinkedList<String> srcSplit = new LinkedList<String>();
LinkedList<String> tgtSplit = new LinkedList<String>();
while(!src.isRoot()) {
srcSplit.addFirst(src.getName());
src = src.getParent();
}
while(!tgt.isRoot()) {
tgtSplit.addFirst(tgt.getName());
tgt = tgt.getParent();
}
boolean share = false;
while(!srcSplit.isEmpty() && !tgtSplit.isEmpty()) {
if(srcSplit.getFirst().equals(tgtSplit.getFirst())) {
srcSplit.removeFirst();
tgtSplit.removeFirst();
share = true;
} else {
break;
}
}
if(!share) {
return null;
}
for(int left = 0; left < srcSplit.size(); left++) {
if(left == 0) {
path += ".."; //NOI18N
} else {
path += "/.."; //NOI18N
}
}
while(!tgtSplit.isEmpty()) {
if(path.isEmpty()) {
path += tgtSplit.getFirst();
} else {
path += "/" + tgtSplit.getFirst(); //NOI18N
}
tgtSplit.removeFirst();
}
if(targetName != null) {
if(!path.isEmpty()) {
path += "/" + targetName; //NOI18N
} else {
path += targetName;
}
}
return path;
}
/**
* Finds the file/dir represented by relPath with respect to sourceDir.
* Returns null if the file does not exist.
*
* @param sourceDir file/dir to which the relative path is related
* @param relPath relative path related to sourceDir
* @return FileObject or null
*/
public static FileObject getFileObject(@NonNull final FileObject sourceDir, @NonNull final String relPath) {
String split[] = relPath.split("[\\\\/]+"); //NOI18N
FileObject src = sourceDir;
String path = ""; //NOI18N
boolean back = true;
if(split[0].equals("..")) {
for(int i = 0; i < split.length; i++) {
if(back && split[i].equals("..")) { //NOI18N
src = src.getParent();
if(src == null) {
return null;
}
} else {
if(back) {
back = false;
path = src.getPath();
}
path += "/" + split[i]; //NOI18N
}
}
} else {
path = relPath;
}
File f = new File(path);
if(f.exists()) {
return FileUtil.toFileObject(f);
}
return null;
}
/**
* Initialize project properties for JavaFX Application project type
* @param ep
* @param type
* @param platformName
* @param mainClass
* @param preloader
* @throws MissingResourceException
*/
static void initializeJavaFXProperties(@NonNull EditableProperties ep, @NonNull WizardType type, String targetPlatformName, @NonNull String mainClass, String preloader) throws MissingResourceException {
ep.setProperty(JFXProjectProperties.JAVAFX_ENABLED, TRUE); // NOI18N
ep.setComment(JFXProjectProperties.JAVAFX_ENABLED, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_javafx")}, false); // NOI18N
ep.setProperty("jnlp.enabled", FALSE); // NOI18N
ep.setComment("jnlp.enabled", new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_oldjnlp")}, false); // NOI18N
ep.setProperty(ProjectProperties.COMPILE_ON_SAVE, TRUE); // NOI18N
ep.setProperty(ProjectProperties.COMPILE_ON_SAVE_UNSUPPORTED_PREFIX + ".javafx", TRUE); // NOI18N
ep.setProperty(JFXProjectProperties.JAVAFX_BINARY_ENCODE_CSS, FALSE); // NOI18N
ep.setProperty(JFXProjectProperties.JAVAFX_DEPLOY_INCLUDEDT, TRUE); // NOI18N
ep.setProperty(JFXProjectProperties.JAVAFX_DEPLOY_EMBEDJNLP, TRUE); // NOI18N
ep.setProperty(JFXProjectProperties.JAVAFX_REBASE_LIBS, FALSE); // NOI18N
ep.setComment(JFXProjectProperties.JAVAFX_REBASE_LIBS, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_rebase_libs")}, false); // NOI18N
ep.setProperty(JFXProjectProperties.JAVAFX_DISABLE_CONCURRENT_RUNS, FALSE); // NOI18N
ep.setComment(JFXProjectProperties.JAVAFX_DISABLE_CONCURRENT_RUNS, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_disable_concurrent_runs")}, false); // NOI18N
ep.setProperty(JFXProjectProperties.JAVAFX_ENABLE_CONCURRENT_EXTERNAL_RUNS, FALSE); // NOI18N
ep.setComment(JFXProjectProperties.JAVAFX_ENABLE_CONCURRENT_EXTERNAL_RUNS, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_enable_concurrent_external_runs")}, false); // NOI18N
ep.setProperty(JFXProjectProperties.UPDATE_MODE_BACKGROUND, FALSE); // NOI18N
ep.setComment(JFXProjectProperties.UPDATE_MODE_BACKGROUND, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, type == WizardType.SWING ? "COMMENT_updatemode_swing" : "COMMENT_updatemode")}, false); // NOI18N
ep.setProperty(JFXProjectProperties.ALLOW_OFFLINE, TRUE); // NOI18N
if(targetPlatformName == null) {
targetPlatformName = ep.getProperty(JFXProjectProperties.PLATFORM_ACTIVE);
}
Collection<String> extensions = JavaFxRuntimeInclusion.getProjectClassPathExtension(JavaFXPlatformUtils.findJavaPlatform(targetPlatformName));
if (extensions != null && !extensions.isEmpty()) {
ep.setProperty(JavaFXPlatformUtils.JAVAFX_CLASSPATH_EXTENSION, JFXProjectUtils.getPaths(extensions));
ep.setProperty(ProjectProperties.JAVAC_CLASSPATH, new String[]{JavaFXPlatformUtils.getClassPathExtensionProperty()});
}
ep.setProperty(ProjectProperties.ENDORSED_CLASSPATH, ""); // NOI18N
ep.setProperty(JFXProjectProperties.RUN_APP_WIDTH, "800"); // NOI18N
ep.setProperty(JFXProjectProperties.RUN_APP_HEIGHT, "600"); // NOI18N
if (type == WizardType.PRELOADER) {
ep.setProperty(JFXProjectProperties.JAVAFX_PRELOADER, TRUE); // NOI18N
ep.setComment(JFXProjectProperties.JAVAFX_PRELOADER, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_preloader")}, false); // NOI18N
ep.setProperty(JFXProjectProperties.PRELOADER_ENABLED, FALSE); // NOI18N
ep.setComment(JFXProjectProperties.PRELOADER_ENABLED, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_prepreloader")}, false); // NOI18N
} else {
ep.setProperty("jar.archive.disabled", TRUE); // NOI18N
ep.setComment("jar.archive.disabled", new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_oldjar")}, false); // NOI18N
ep.setProperty(ProjectProperties.MAIN_CLASS, type == WizardType.SWING ? (mainClass == null ? "" : mainClass) : "com.javafx.main.Main"); // NOI18N
ep.setComment(ProjectProperties.MAIN_CLASS, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_main.class")}, false); // NOI18N
if (type != WizardType.LIBRARY) {
ep.setProperty(JFXProjectProperties.MAIN_CLASS, mainClass == null ? "" : mainClass); // NOI18N
ep.setComment(JFXProjectProperties.MAIN_CLASS, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_main.fxclass")}, false); // NOI18N
}
if (preloader != null && preloader.length() > 0) {
String preloaderProjRelative = "../" + preloader; // NOI18N
String preloaderJarFileName = preloader + ".jar"; // NOI18N
String copiedPreloaderJarPath = "${dist.dir}/lib/${" + JFXProjectProperties.PRELOADER_JAR_FILENAME + "}"; // NOI18N
ep.setProperty(JFXProjectProperties.PRELOADER_ENABLED, TRUE); // NOI18N
ep.setProperty(JFXProjectProperties.PRELOADER_TYPE, JFXProjectProperties.PreloaderSourceType.PROJECT.getString());
ep.setComment(JFXProjectProperties.PRELOADER_ENABLED, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_use_preloader")}, false); // NOI18N
ep.setProperty(JFXProjectProperties.PRELOADER_PROJECT, preloaderProjRelative);
ep.setProperty(JFXProjectProperties.PRELOADER_CLASS, JavaFXProjectWizardIterator.generatePreloaderClassName(preloader));
ep.setProperty(JFXProjectProperties.PRELOADER_JAR_PATH, copiedPreloaderJarPath);
ep.setProperty(JFXProjectProperties.PRELOADER_JAR_FILENAME, preloaderJarFileName);
} else {
ep.setProperty(JFXProjectProperties.PRELOADER_ENABLED, FALSE); // NOI18N
ep.setProperty(JFXProjectProperties.PRELOADER_TYPE, JFXProjectProperties.PreloaderSourceType.NONE.getString());
ep.setComment(JFXProjectProperties.PRELOADER_ENABLED, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_dontuse_preloader")}, false); // NOI18N
ep.setProperty(JFXProjectProperties.PRELOADER_PROJECT, ""); // NOI18N
ep.setProperty(JFXProjectProperties.PRELOADER_CLASS, ""); // NOI18N
ep.setProperty(JFXProjectProperties.PRELOADER_JAR_PATH, ""); // NOI18N
ep.setProperty(JFXProjectProperties.PRELOADER_JAR_FILENAME, ""); // NOI18N
}
if (type == WizardType.SWING) {
ep.setProperty(JFXProjectProperties.JAVAFX_SWING, TRUE); // NOI18N
ep.setComment(JFXProjectProperties.JAVAFX_SWING, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_use_swing")}, false); // NOI18N
}
}
ep.setProperty(JFXProjectProperties.IMPLEMENTATION_VERSION, JFXProjectProperties.IMPLEMENTATION_VERSION_DEFAULT);
ep.setProperty(JFXProjectProperties.FALLBACK_CLASS, "com.javafx.main.NoJavaFXFallback"); // NOI18N
// extended manifest entries
ep.setProperty(JFXProjectProperties.MANIFEST_CUSTOM_CODEBASE, "*"); // NOI18N
ep.setComment(JFXProjectProperties.MANIFEST_CUSTOM_CODEBASE, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_manifest_custom_codebase")}, false); // NOI18N
ep.setProperty(JFXProjectProperties.MANIFEST_CUSTOM_PERMISSIONS, ""); // NOI18N
ep.setComment(JFXProjectProperties.MANIFEST_CUSTOM_PERMISSIONS, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_manifest_custom_permissions")}, false); // NOI18N
// SE->FX conversion cleanup
ep.remove(JFXProjectProperties.JAVASE_KEEP_JFXRT_ON_CLASSPATH);
}
/**
* Modify project.properties file to contain all javafx relevant properties with initial values
* @param project
* @throws IOException
*/
public static void resetJavaFXProjectProperties(@NonNull final Project project, @NonNull final WizardType type, final String targetPlatformName, @NonNull final String mainClass, final String preloader) throws IOException {
final EditableProperties ep = new EditableProperties(true);
final FileObject projPropsFO = project.getProjectDirectory().getFileObject(AntProjectHelper.PROJECT_PROPERTIES_PATH);
try {
ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() {
@Override
public Void run() throws Exception {
final Lookup lookup = project.getLookup();
final J2SEPropertyEvaluator eval = lookup.lookup(J2SEPropertyEvaluator.class);
String platformNameToCheck = null;
if (eval != null) {
String currentPlatformName = eval.evaluator().getProperty(JFXProjectProperties.PLATFORM_ACTIVE);
platformNameToCheck = currentPlatformName;
JavaPlatform platform = null;
if(targetPlatformName == null) {
platform = JavaFXPlatformUtils.findJavaPlatform(currentPlatformName);
} else {
platform = JavaFXPlatformUtils.findJavaPlatform(targetPlatformName);
}
if(platform == null || !JavaFXPlatformUtils.isJavaFXEnabled(platform)) {
platform = JavaFXPlatformUtils.findJavaFXPlatform();
}
if(platform != null && !JFXProjectProperties.isEqual(currentPlatformName, JavaFXPlatformUtils.getPlatformAntName(platform))) {
final J2SEProjectPlatform platformSetter = lookup.lookup(J2SEProjectPlatform.class);
if(platformSetter != null) {
platformSetter.setProjectPlatform(platform);
platformNameToCheck = JavaFXPlatformUtils.getPlatformAntName(platform);
}
}
}
final InputStream is = projPropsFO.getInputStream();
try {
ep.load(is);
} finally {
if (is != null) {
is.close();
}
}
initializeJavaFXProperties(ep, type, platformNameToCheck, mainClass, preloader);
OutputStream os = null;
FileLock lock = null;
try {
lock = projPropsFO.lock();
os = projPropsFO.getOutputStream(lock);
ep.store(os);
} finally {
if (os != null) {
os.close();
}
if (lock != null) {
lock.releaseLock();
}
}
return null;
}
});
} catch (MutexException mux) {
throw (IOException) mux.getException();
}
}
/**
* Adds FX specific build script jfx-impl.xml to project build system
* @param p
* @param dirFO
* @param type
* @throws IOException
*/
static void createJfxExtension(Project p, FileObject dirFO, WizardType type) throws IOException {
FileObject templateFO = FileUtil.getConfigFile("Templates/JFX/jfx-impl.xml"); // NOI18N
if (templateFO != null) {
FileObject nbprojectFO = dirFO.getFileObject("nbproject"); // NOI18N
FileObject jfxBuildFile = FileUtil.copyFile(templateFO, nbprojectFO, "jfx-impl"); // NOI18N
if (type == JavaFXProjectWizardIterator.WizardType.SWING) {
FileObject templatesFO = nbprojectFO.getFileObject("templates"); // NOI18N
if (templatesFO == null) {
templatesFO = nbprojectFO.createFolder("templates"); // NOI18N
}
FileObject swingTemplateFO1 = FileUtil.getConfigFile("Templates/JFX/FXSwingTemplate.html"); // NOI18N
if (swingTemplateFO1 != null) {
FileUtil.copyFile(swingTemplateFO1, templatesFO, "FXSwingTemplate"); // NOI18N
}
FileObject swingTemplateFO2 = FileUtil.getConfigFile("Templates/JFX/FXSwingTemplateApplet.jnlp"); // NOI18N
if (swingTemplateFO1 != null) {
FileUtil.copyFile(swingTemplateFO2, templatesFO, "FXSwingTemplateApplet"); // NOI18N
}
FileObject swingTemplateFO3 = FileUtil.getConfigFile("Templates/JFX/FXSwingTemplateApplication.jnlp"); // NOI18N
if (swingTemplateFO1 != null) {
FileUtil.copyFile(swingTemplateFO3, templatesFO, "FXSwingTemplateApplication"); // NOI18N
}
}
JFXProjectUtils.addExtension(p);
}
}
/**
* Adds dependencies of build-impl targets on jfx-impl targets
* @param proj
* @return true if success
* @throws IOException
*/
static boolean addExtension(@NonNull Project proj) throws IOException {
boolean res = false;
FileObject projDir = proj.getProjectDirectory();
FileObject jfxBuildFile = projDir.getFileObject(JFX_BUILD_IMPL_PATH);
AntBuildExtender extender = proj.getLookup().lookup(AntBuildExtender.class);
if (extender != null) {
assert jfxBuildFile != null;
if (extender.getExtension(CURRENT_EXTENSION) == null) { // NOI18N
AntBuildExtender.Extension ext = extender.addExtension(CURRENT_EXTENSION, jfxBuildFile); // NOI18N
// NOTE: change in dependencies = change of metafile updates API;
// do not forget to update CURRENT_EXTENSION and add the old one to OLD_EXTENSIONS
ext.addDependency("-post-jar", "-jfx-copylibs"); // NOI18N
ext.addDependency("-post-jar", "-rebase-libs"); //NOI18N
ext.addDependency("-post-jar", "jfx-deployment"); //NOI18N
ext.addDependency("run", "jar"); //NOI18N
ext.addDependency("debug", "jar");//NOI18N
ext.addDependency("profile", "jar");//NOI18N
res = true;
}
}
return res;
}
/**
* Remove SE native packaging extension and build script
* @param project
*/
static void removeSENativeBundlerExtension(@NonNull Project project) throws IOException {
final AntBuildExtender extender = project.getLookup().lookup(AntBuildExtender.class);
if (extender != null) {
AntBuildExtender.Extension extension = extender.getExtension(J2SEDEPLOY_EXTENSION);
if (extension != null) {
extender.removeExtension(J2SEDEPLOY_EXTENSION);
}
final FileObject buildExFo = project.getProjectDirectory().getFileObject(EXTENSION_BUILD_SCRIPT_PATH);
if (buildExFo != null) {
buildExFo.delete();
}
}
}
/**
* Modify Java Application (SE) project to become JavaFX Application, i.e.,
* set Application class, create FX build scripts, modify properties,
* and turn on FX mode by setting property javafx.enabled=true;
* @param project
* @throws IOException
*/
public static void switchProjectToFX(@NonNull final Project project, @NonNull final JSEApplicationClassChooser chooser) throws IOException {
final FileObject dirFO = project.getProjectDirectory();
dirFO.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() {
@Override
public void run() throws IOException {
removeSENativeBundlerExtension(project);
createJfxExtension(project, dirFO, WizardType.APPLICATION);
ProjectManager.getDefault().saveProject(project);
updateJFXExtension(project);
//set main class
String appClass = chooser.getSelectedExistingClass();
if(appClass == null) {
FileObject dirController = chooser.getCurrentPackageFolder(true);
DataFolder packageDataFolder = DataFolder.findFolder(dirController);
String newClassName = chooser.getCurrentFileName();
if (packageDataFolder != null) {
FileObject javaTemplate = FileUtil.getConfigFile("Templates/javafx/FXMain.java"); // NOI18N
DataObject dJavaTemplate = DataObject.find(javaTemplate);
DataObject dobj = dJavaTemplate.createFromTemplate(packageDataFolder, newClassName);
}
appClass = chooser.getPackageName() + "." + newClassName; // NOI18N
}
resetJavaFXProjectProperties(project, WizardType.APPLICATION, null, appClass, null); // NOI18N
for (ProjectIconAnnotator annotator : Lookup.getDefault().lookupAll(ProjectIconAnnotator.class)) {
if(annotator instanceof JFXProjectIconAnnotator) {
JFXProjectIconAnnotator fxAnnotator = (JFXProjectIconAnnotator) annotator;
fxAnnotator.fireChange(project, true);
}
}
}
});
final String headerTemplate = NbBundle.getMessage(JFXProjectUtils.class, "TXT_SWITCHED_SE_TO_FX_HEADER"); //NOI18N
final String header = MessageFormat.format(headerTemplate, new Object[] {ProjectUtils.getInformation(project).getDisplayName()});
final String content = NbBundle.getMessage(JFXProjectUtils.class, "TXT_SWITCHED_SE_TO_FX_CONTENT"); //NOI18N
Notification notePlatformChange = NotificationDisplayer.getDefault().notify(
header,
ImageUtilities.loadImageIcon("org/netbeans/modules/javafx2/project/ui/resources/jfx_project.png", true), //NOI18N
content,
null,
NotificationDisplayer.Priority.LOW,
NotificationDisplayer.Category.INFO);
JFXProjectOpenedHook.addNotification(project, notePlatformChange);
}
/**
* Update dependencies of build-impl targets on jfx-impl targets
* @param project
* @return
* @throws IOException
*/
public static boolean updateJFXExtension(final Project project) throws IOException {
boolean changed = modifyBuildXml(project);
return changed;
}
/**
* Update SE buildscript dependencies on FX buildscript
* @param proj
* @return
* @throws IOException
*/
private static boolean modifyBuildXml(Project proj) throws IOException {
boolean res = false;
final FileObject buildXmlFO = getBuildXml(proj);
if (buildXmlFO == null) {
LOGGER.warning("The project build script does not exist, the project cannot be extended by JFX."); //NOI18N
return res;
}
Document xmlDoc = null;
try {
xmlDoc = XMLUtil.parse(new InputSource(buildXmlFO.toURL().toExternalForm()), false, true, null, null);
} catch (SAXException ex) {
Exceptions.printStackTrace(ex);
}
if(!addExtension(proj)) {
LOGGER.log(Level.INFO,
"Trying to include JFX build snippet in project type that doesn't support AntBuildExtender API contract."); // NOI18N
}
//TODO this piece shall not proceed when the upgrade to j2se-project/4 was cancelled.
//how to figure..
Element docElem = xmlDoc.getDocumentElement();
NodeList nl = docElem.getElementsByTagName("target"); // NOI18N
boolean changed = false;
nl = docElem.getElementsByTagName("import"); // NOI18N
for (int i = 0; i < nl.getLength(); i++) {
Element e = (Element) nl.item(i);
if (e.getAttribute("file") != null && JFX_BUILD_IMPL_PATH.equals(e.getAttribute("file"))) { // NOI18N
e.getParentNode().removeChild(e);
changed = true;
break;
}
}
if (changed) {
final Document fdoc = xmlDoc;
try {
ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() {
@Override
public Void run() throws Exception {
FileLock lock = buildXmlFO.lock();
try {
OutputStream os = buildXmlFO.getOutputStream(lock);
try {
XMLUtil.write(fdoc, os, "UTF-8"); // NOI18N
} finally {
os.close();
}
} finally {
lock.releaseLock();
}
return null;
}
});
} catch (MutexException mex) {
throw (IOException) mex.getException();
}
}
return res;
}
/**
* Get build.xml
* @param prj
* @return
*/
private static FileObject getBuildXml(final Project prj) {
final J2SEPropertyEvaluator j2sepe = prj.getLookup().lookup(J2SEPropertyEvaluator.class);
assert j2sepe != null;
final PropertyEvaluator eval = j2sepe.evaluator();
String buildScriptPath = eval.getProperty(JFXProjectProperties.BUILD_SCRIPT);
if (buildScriptPath == null) {
buildScriptPath = GeneratedFilesHelper.BUILD_XML_PATH;
}
return prj.getProjectDirectory().getFileObject (buildScriptPath);
}
/**
* Update FX build script.
* @param proj
* @return
* @throws IOException
*/
public static FileObject updateJfxImpl(final @NonNull Project proj) throws IOException {
final FileObject projDir = proj.getProjectDirectory();
final List<FileObject> updates = new ArrayList<FileObject>();
try {
ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() {
@Override
public Void run() throws Exception {
projDir.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() {
public void run() throws IOException {
if(!JFXProjectUtils.isJFXImplCurrent(proj)) {
FileObject updated = JFXProjectUtils.doUpdateJfxImpl(proj);
if(updated != null) {
updates.add(updated);
}
}
}
});
return null;
}
});
} catch (MutexException ex) {
Exceptions.printStackTrace(ex);
}
return updates.isEmpty() ? null : updates.get(0);
}
/**
* Checks whether file nbproject/jfx-impl.xml equals current template.
* @param proj
* @return
* @throws IOException
*/
public static boolean isJFXImplCurrent(final @NonNull Project proj) throws IOException {
Boolean isJfxCurrent = true;
final FileObject projDir = proj.getProjectDirectory();
try {
isJfxCurrent = ProjectManager.mutex().readAccess(new Mutex.ExceptionAction<Boolean>() {
@Override
public Boolean run() throws Exception {
FileObject jfxBuildFile = projDir.getFileObject(JFX_BUILD_IMPL_PATH); // NOI18N
Boolean isCurrent = false;
if (jfxBuildFile != null) {
final InputStream in = jfxBuildFile.getInputStream();
if(in != null) {
try {
isCurrent = isJfxImplCurrentVer(computeCrc32( in ));
} finally {
in.close();
}
}
}
return isCurrent;
}
});
} catch (MutexException mux) {
isJfxCurrent = false;
LOGGER.log(Level.INFO, "Problem reading " + JFX_BUILD_IMPL_PATH, mux.getException()); // NOI18N
}
return isJfxCurrent;
}
/**
* The file nbproject/jfx-impl.xml is backed up and regenerated to the current state
* and textual commentary is generated to UPDATED.TXT.
*
* @param prj the project to check
* @return FileObject pointing at generated UPDATED.TXT or null
*/
static FileObject doUpdateJfxImpl(final @NonNull Project proj) throws IOException {
FileObject returnFO = null;
final FileObject projDir = proj.getProjectDirectory();
try {
returnFO = ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<FileObject>() {
@Override
public FileObject run() throws Exception {
FileObject returnFO = null;
FileObject jfxBuildFile = projDir.getFileObject(JFX_BUILD_IMPL_PATH); // NOI18N
if (jfxBuildFile != null) {
// try to close the file just in case the file is already opened in editor
DataObject dobj = DataObject.find(jfxBuildFile);
CloseCookie closeCookie = dobj.getLookup().lookup(CloseCookie.class);
if (closeCookie != null) {
closeCookie.close();
}
closeCookie = null;
dobj = null;
final FileObject nbproject = projDir.getFileObject(NBPROJECT); //NOI18N
final String backupName = FileUtil.findFreeFileName(nbproject, JFX_BUILD_IMPL_NAME + "_backup", "xml"); //NOI18N
FileUtil.moveFile(jfxBuildFile, nbproject, backupName);
LOGGER.log(Level.INFO, "Old build script file " + JFX_BUILD_IMPL_NAME + ".xml has been renamed to: {0}.xml", backupName); // NOI18N
jfxBuildFile = null;
try {
final File readme = new File (FileUtil.toFile(nbproject), NbBundle.getMessage(JFXProjectUtils.class, "TXT_UPDATED_README_FILE_NAME")); //NOI18N
if (!readme.exists()) {
readme.createNewFile();
}
final FileObject readmeFO = FileUtil.toFileObject(readme);
returnFO = readmeFO;
OutputStream os = null;
FileLock lock = null;
try {
lock = readmeFO.lock();
os = readmeFO.getOutputStream(lock);
final PrintWriter out = new PrintWriter ( os );
try {
final String headerTemplate = NbBundle.getMessage(JFXProjectUtils.class, "TXT_UPDATED_README_FILE_CONTENT_HEADER"); //NOI18N
final String header = MessageFormat.format(headerTemplate, new Object[] {ProjectUtils.getInformation(proj).getDisplayName()});
char[] underline = new char[header.length()];
Arrays.fill(underline, '='); // NOI18N
final String content = NbBundle.getMessage(JFXProjectUtils.class, "TXT_UPDATED_README_FILE_CONTENT"); //NOI18N
out.println(underline);
out.println(header);
out.println(underline);
out.println (MessageFormat.format(content, new Object[] {backupName + ".xml"})); //NOI18N
} finally {
if(out != null) {
out.close ();
}
}
} finally {
if (os != null) {
os.close();
}
if (lock != null) {
lock.releaseLock();
}
}
} catch (IOException ioe) {
LOGGER.log(Level.INFO, "Cannot create file readme file. ", ioe); // NOI18N
}
}
if (jfxBuildFile == null) {
FileObject templateFO = FileUtil.getConfigFile(JFX_BUILD_TEMPLATE);
if (templateFO != null) {
FileUtil.copyFile(templateFO, projDir.getFileObject(NBPROJECT), JFX_BUILD_IMPL_NAME); // NOI18N
LOGGER.log(Level.INFO, "Build script " + JFX_BUILD_IMPL_NAME + ".xml has been updated to the latest version supported by this NetBeans installation."); // NOI18N
}
}
return returnFO;
} //run()
});
} catch(MutexException mux) {
throw (IOException) mux.getException();
}
return returnFO;
}
/**
* Computes CRC code of data from InputStream
*
* @param is InputStream to read data from
* @return CRC code
*/
public static String computeCrc32(InputStream is) throws IOException {
Checksum crc = new CRC32();
int last = -1;
int curr;
while ((curr = is.read()) != -1) {
if (curr != '\n' && last == '\r') {
crc.update('\n');
}
if (curr != '\r') {
crc.update(curr);
}
last = curr;
}
if (last == '\r') {
crc.update('\n');
}
int val = (int)crc.getValue();
String hex = Integer.toHexString(val);
while (hex.length() < 8) {
hex = "0" + hex; // NOI18N
}
return hex;
}
/**
* Checks whether crc is the CRC code of current jfx-impl.xml template.
*
* @param crc code to be compared against
* @return true if crc is the CRC code of current jfx-impl.xml template.
*/
public static boolean isJfxImplCurrentVer(String crc) throws IOException {
String _currentJfxImplCRC = currentJfxImplCRCCache;
if (_currentJfxImplCRC == null) {
final FileObject template = FileUtil.getConfigFile(JFX_BUILD_TEMPLATE);
final InputStream in = template.getInputStream();
if(in != null) {
try {
currentJfxImplCRCCache = _currentJfxImplCRC = computeCrc32(in);
} finally {
in.close();
}
}
}
return _currentJfxImplCRC.equals(crc);
}
public static boolean isProjectOpen(@NonNull Project project) {
Project[] projects = OpenProjects.getDefault().getOpenProjects();
if(projects != null) {
for(Project p : Arrays.asList(projects)) {
if(p.getProjectDirectory().getPath().equals(project.getProjectDirectory().getPath())) {
return true;
}
}
}
return false;
}
/**
* Updates project.properties so that if JFXRT artifacts are explicitly added to
* compile classpath if they are not on classpath by default. This is a workaround
* of the fact that JDK1.7 does contain FX RT but does not provide it on classpath.
* JDK1.8 has FX RT on classpath, but still may not include all relevant artifacts by default
* and may need this extension.
* Note that this extension is relevant not only for FX Application projects, but also
* for SE projects that have the property "keep.javafx.runtime.on.classpath" set
* (see SE Deployment category in Project Properties dialog).
*
* @param prj the project to update
*/
public static void updateClassPathExtension(@NonNull final Project project) throws IOException {
final boolean hasDefaultJavaFXPlatform[] = new boolean[1];
final EditableProperties ep = new EditableProperties(true);
final FileObject projPropsFO = project.getProjectDirectory().getFileObject(AntProjectHelper.PROJECT_PROPERTIES_PATH);
if(projPropsFO != null) {
try {
ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() {
@Override
public Void run() throws Exception {
final Lookup lookup = project.getLookup();
final J2SEPropertyEvaluator eval = lookup.lookup(J2SEPropertyEvaluator.class);
if (eval != null) {
// if project has Default_JavaFX_Platform, change it to default Java Platform
String platformName = eval.evaluator().getProperty(JFXProjectProperties.PLATFORM_ACTIVE);
hasDefaultJavaFXPlatform[0] = JFXProjectProperties.isEqual(platformName, JavaFXPlatformUtils.DEFAULT_JAVAFX_PLATFORM);
}
if(hasDefaultJavaFXPlatform[0]) {
final J2SEProjectPlatform platformSetter = lookup.lookup(J2SEProjectPlatform.class);
if(platformSetter != null) {
platformSetter.setProjectPlatform(JavaPlatformManager.getDefault().getDefaultPlatform());
}
}
try (InputStream is = projPropsFO.getInputStream()) {
ep.load(is);
}
boolean cpExtUpdated = updateClassPathExtensionProperties(ep);
if (cpExtUpdated) {
final FileLock lock = projPropsFO.lock();
try (OutputStream os =projPropsFO.getOutputStream(lock)) {
ep.store(os);
} finally {
lock.releaseLock();
}
}
return null;
}
});
} catch (MutexException mux) {
throw (IOException) mux.getException();
}
if(hasDefaultJavaFXPlatform[0]) {
final String headerTemplate = NbBundle.getMessage(JFXProjectUtils.class, "TXT_UPDATED_DEFAULT_PLATFORM_HEADER"); //NOI18N
final String header = MessageFormat.format(headerTemplate, new Object[] {ProjectUtils.getInformation(project).getDisplayName()});
final String content = NbBundle.getMessage(JFXProjectUtils.class, "TXT_UPDATED_DEFAULT_PLATFORM_CONTENT"); //NOI18N
Notification notePlatformChange = NotificationDisplayer.getDefault().notify(
header,
ImageUtilities.loadImageIcon("org/netbeans/modules/javafx2/project/ui/resources/jfx_project.png", true), //NOI18N
content,
null,
NotificationDisplayer.Priority.LOW,
NotificationDisplayer.Category.INFO);
JFXProjectOpenedHook.addNotification(project, notePlatformChange);
}
} else {
LOGGER.warning("Project metafiles inaccessible - classpath extension could not be verified and updated if needed."); //NOI18N
}
}
/**
* Update main class property in project metafiles in the current configuration
* @param project
* @param mainClass
* @throws IOException
*/
public static void updatePropertyInActiveConfig(@NonNull final Project project, @NonNull final String propName, @NonNull final String propValue) throws IOException {
final FileObject projectDir = project.getProjectDirectory();
try {
ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() {
@Override
public Void run() throws Exception {
final EditableProperties cep = new EditableProperties(true);
final FileObject configFO = projectDir.getFileObject(JFXProjectConfigurations.CONFIG_PROPERTIES_FILE);
if(configFO != null) {
final InputStream cis = configFO.getInputStream();
try {
cep.load(cis);
} finally {
if (cis != null) {
cis.close();
}
}
}
String config = cep.getProperty(ProjectProperties.PROP_PROJECT_CONFIGURATION_CONFIG);
final FileObject projPropsFO = config == null ? projectDir.getFileObject(AntProjectHelper.PROJECT_PROPERTIES_PATH) :
projectDir.getFileObject(JFXProjectConfigurations.PROJECT_CONFIGS_DIR +
"/" + config + "." + JFXProjectConfigurations.PROPERTIES_FILE_EXT); //NOI18N
assert projPropsFO != null : "Inaccessible file " + JFXProjectConfigurations.PROJECT_CONFIGS_DIR + //NOI18N
"/" + config + "." + JFXProjectConfigurations.PROPERTIES_FILE_EXT; //NOI18N
final InputStream is = projPropsFO.getInputStream();
final EditableProperties ep = new EditableProperties(true);
try {
ep.load(is);
} finally {
if (is != null) {
is.close();
}
}
ep.setProperty(propName, propValue);
OutputStream os = null;
FileLock lock = null;
try {
lock = projPropsFO.lock();
os = projPropsFO.getOutputStream(lock);
ep.store(os);
} finally {
if (os != null) {
os.close();
}
if (lock != null) {
lock.releaseLock();
}
}
return null;
}
});
} catch (MutexException mux) {
throw (IOException) mux.getException();
}
}
/**
* Create array of Strings representing path artifacts that can be set to path property;
* all but the last String gets : appended.
* @param artifacts
* @return array of artifacts
*/
public static String[] getPaths(@NonNull final Collection<String> artifacts) {
List<String> l = new ArrayList<String>();
Iterator<String> i = artifacts.iterator();
while(i.hasNext()) {
String s = i.next();
if(i.hasNext()) {
if(s.endsWith(":")) { //NOI18N
l.add(s);
} else {
l.add(s + ":"); //NOI18N
}
} else {
if(s.endsWith(":")) { //NOI18N
l.add(s.substring(0, s.length()-1));
} else {
l.add(s);
}
}
}
return l.toArray(new String[0]);
}
/**
* Remove trailing : from array of path artifacts and return the array as collection
* @param artifacts
* @return
*/
@NonNull
public static Set<String> getPaths(@NonNull final String[] artifacts) {
final Set<String> l = new LinkedHashSet<>();
for(String artifact : artifacts) {
if(artifact.endsWith(":")) { //NOI18N
l.add(artifact.substring(0, artifact.length()-1));
} else {
l.add(artifact);
}
}
return l;
}
/**
* Filter out artifacts that contain subString
* @param artifacts
* @return set without artifacts that contain subString
*/
private static Set<String> filterOutArtifacts(@NonNull final Collection<String> artifacts, @NonNull String subString) {
Set<String> result = new LinkedHashSet<String>();
for(String artifact : artifacts) {
if(!artifact.contains(subString)) {
result.add(artifact);
}
}
return result;
}
/**
* Returns existing value of a path property, null if it does not exist
* @param ep EditableProperties
* @return collection of artifacts or null
*/
@CheckForNull
public static Set<String> getExistingProperty(@NonNull final EditableProperties ep, @NonNull String propName) {
// existing
String currentPropVal = ep.getProperty(propName);
if(currentPropVal != null) {
return Collections.unmodifiableSet(getPaths(PropertyUtils.tokenizePath(currentPropVal)));
}
return null;
}
/**
* Returns updated value of classpath property, null if it should not exist
* @param ep EditableProperties
* @return collection of artifacts or null
*/
public static Set<String> getUpdatedCPProperty(@NonNull final EditableProperties ep, boolean extensionPropertyEmpty) {
boolean extensionNeeded = JFXProjectProperties.isTrue(ep.getProperty(JFXProjectProperties.JAVAFX_ENABLED)) ||
JFXProjectProperties.isTrue(ep.getProperty(JFXProjectProperties.JAVASE_KEEP_JFXRT_ON_CLASSPATH));
// existing
Set<String> existing = getExistingProperty(ep, ProjectProperties.JAVAC_CLASSPATH);
Set<String> updated = new LinkedHashSet<String>();
if(existing != null) {
updated = filterOutArtifacts(existing, "${javafx.runtime}"); // NOI18N
updated = filterOutArtifacts(updated, JavaFXPlatformUtils.getClassPathExtensionProperty());
}
if(extensionNeeded && !extensionPropertyEmpty) {
updated.add(JavaFXPlatformUtils.getClassPathExtensionProperty());
}
return Collections.unmodifiableSet(updated);
}
/**
* Removes the path entry from path.
* @param path to remove the netry from
* @param toRemove the entry to be rmeoved from the path
* @return new path with removed entry
*/
@NonNull
public static String removeFromPath(@NonNull final String path, @NonNull final String toRemove) {
Parameters.notNull("path", path); //NOI18N
Parameters.notNull("toRemove", toRemove); //NOI18N
final StringBuilder sb = new StringBuilder();
for (String entry : PropertyUtils.tokenizePath(path)) {
if (toRemove.equals(entry)) {
continue;
}
sb.append(entry);
sb.append(':'); //NOI18N
}
return sb.length() == 0 ?
sb.toString() :
sb.substring(0, sb.length()-1);
}
/**
* Returns new value of FX artifacts extension property if it needs to be updated, null otherwise
* @param ep EditableProperties
* @return collection of artifacts or null
*/
public static Set<String> getUpdatedExtensionProperty(@NonNull final EditableProperties ep) throws IllegalArgumentException {
boolean propertyNeeded = JFXProjectProperties.isTrue(ep.getProperty(JFXProjectProperties.JAVAFX_ENABLED)) ||
JFXProjectProperties.isTrue(ep.getProperty(JFXProjectProperties.JAVASE_KEEP_JFXRT_ON_CLASSPATH));
// expected
Set<String> updated = null;
if(propertyNeeded) {
String platformName = ep.getProperty(JFXProjectProperties.PLATFORM_ACTIVE);
JavaPlatform platform = JavaFXPlatformUtils.findJavaPlatform(platformName);
if(platform == null) {
// platform does not exist, thus no extension property update is to take place
return null;
}
updated = JavaFxRuntimeInclusion.getProjectClassPathExtension(platform);
}
return updated == null ? null : Collections.unmodifiableSet(updated);
}
/**
* Compares two Set representations of a path property
* @param set1
* @param set2
* @return true if the two sets represent equal existence and values of path properties
*/
public static boolean pathPropertiesEqual(Set<String> set1, Set<String> set2) {
if(set1 == null && set2 == null) {
return true;
}
if(set1 == null || set2 == null) {
return false;
}
return set1.containsAll(set2) && set2.containsAll(set1);
}
/**
* Checks whether JFXRT artifacts are properly added to classpath and represented
* in project.properties if the current Java Platform needs this workaround
* @param prj the project to check
* @return true if project properties correctly represent JFXRT artifacts
*/
public static boolean hasCorrectClassPathExtension(@NonNull final Project project) throws IOException, IllegalArgumentException {
final EditableProperties ep = readFromFile(
project.getProjectDirectory().getFileObject(AntProjectHelper.PROJECT_PROPERTIES_PATH)
);
String platformName = ep.getProperty(JFXProjectProperties.PLATFORM_ACTIVE);
if(JFXProjectProperties.isEqual(platformName, JavaFXPlatformUtils.DEFAULT_JAVAFX_PLATFORM)) {
// request auto-replace of Default_JavaFX_Platform by default platform
return false;
}
Set<String> existingExt = getExistingProperty(ep, JavaFXPlatformUtils.JAVAFX_CLASSPATH_EXTENSION);
Set<String> updatedExt = getUpdatedExtensionProperty(ep);
Set<String> existingCP = getExistingProperty(ep, ProjectProperties.JAVAC_CLASSPATH);
Set<String> updatedCP = getUpdatedCPProperty(ep, updatedExt == null || updatedExt.isEmpty());
return pathPropertiesEqual(existingExt, updatedExt) && pathPropertiesEqual(existingCP, updatedCP);
}
/**
* Updates EditableProperties so that JFXRT artifacts are explicitly added to
* compile classpath if they are not on classpath by default. This is a workaround
* of the fact that JDK1.7 does contain FX RT but does not provide it on classpath.
* JDK1.8 has FX RT on classpath, but still may not include all relevant artifacts by default
* and may need this extension.
* Note that this extension is relevant not only for FX Application projects, but also
* for SE projects that have the property "keep.javafx.runtime.on.classpath" set
* (see SE Deployment category in Project Properties dialog).
*
* @param ep EditableProperties containing properties to be updated
*/
public static boolean updateClassPathExtensionProperties(@NonNull final EditableProperties ep) {
boolean changed = false;
changed = ep.remove(PROPERTY_JAVAFX_RUNTIME) != null ? true : changed;
changed = ep.remove(PROPERTY_JAVAFX_SDK) != null ? true : changed;
Collection<String> extendExtProp = getUpdatedExtensionProperty(ep);
if(extendExtProp != null && !extendExtProp.isEmpty()) {
Collection<String> currentCpExt = getExistingProperty(ep, JavaFXPlatformUtils.JAVAFX_CLASSPATH_EXTENSION);
if (!extendExtProp.equals(currentCpExt)) {
//update javafx.classpath.extension only in case that current value doesn't contain required jars
ep.setProperty(JavaFXPlatformUtils.JAVAFX_CLASSPATH_EXTENSION, getPaths(extendExtProp));
changed = true;
}
} else {
changed = ep.remove(JavaFXPlatformUtils.JAVAFX_CLASSPATH_EXTENSION) != null ? true : changed;
//ep.remove(JFXProjectProperties.JAVASE_KEEP_JFXRT_ON_CLASSPATH);
}
Collection<String> extendCPProp = getUpdatedCPProperty(ep, extendExtProp == null || extendExtProp.isEmpty());
// JAVAC_CLASSPATH to be preserved even if empty (create new project creates it empty by default)
if(extendCPProp != null) {
Collection<String> currentJavacCp = getExistingProperty(ep, ProjectProperties.JAVAC_CLASSPATH);
if (!extendCPProp.equals(currentJavacCp)) {
ep.setProperty(ProjectProperties.JAVAC_CLASSPATH, getPaths(extendCPProp));
changed = true;
}
}
// Remove JavaFX endorsed.classpath entries if they are present (#235380, see also #214386)
final String endorsedCp = ep.get(ProjectProperties.ENDORSED_CLASSPATH);
if (endorsedCp != null && !endorsedCp.isEmpty()) {
String[] cpElements = PropertyUtils.tokenizePath(endorsedCp);
List<String> updatedEndorsedCpList = new ArrayList<String>();
for (String element : cpElements) {
if (!element.startsWith("${javafx.runtime}/")) { //NOI18N
updatedEndorsedCpList.add(element);
}
}
String[] updatedEndorsedCp = updatedEndorsedCpList.toArray(new String[0]);
for (int i = 0; i < updatedEndorsedCp.length - 1; i++) {
updatedEndorsedCp[i] += ":"; //NOI18N
}
ep.setProperty(ProjectProperties.ENDORSED_CLASSPATH, updatedEndorsedCp);
changed = true;
}
return changed;
}
/**
* Checks the existence of default configuration for given RUN_AS type.
* If it does not exist, it is created.
*
* @param proj the project to check
* @param runAs run type whose default configuration is to be updated
* @param setBrowserProps if true, adds properties representing an existing browser
* @return true is any update took place
*/
public static boolean updateDefaultRunAsConfigFile(final @NonNull FileObject projDir, JFXProjectProperties.RunAsType runAs, boolean setBrowserProps) throws IOException {
boolean updated = false;
String configName = runAs.getDefaultConfig();
String configFile = makeSafe(configName);
String sharedPath = JFXProjectConfigurations.getSharedConfigFilePath(configFile);
FileObject sharedCfgFO = projDir.getFileObject(sharedPath);
final EditableProperties sharedCfgProps = sharedCfgFO != null ?
readFromFile(sharedCfgFO) : new EditableProperties(true);
assert sharedCfgProps != null;
if(sharedCfgProps.isEmpty()) {
sharedCfgProps.setProperty("$label", configName); // NOI18N
sharedCfgProps.setComment("$label", new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_run_as_defaults")}, false); // NOI18N
saveToFile(projDir, sharedPath, sharedCfgProps);
updated = true;
}
String privatePath = JFXProjectConfigurations.getPrivateConfigFilePath(configFile);
FileObject privateCfgFO = projDir.getFileObject(privatePath);
final EditableProperties privateCfgProps = privateCfgFO != null ?
readFromFile(projDir, privatePath) : new EditableProperties(true);
assert privateCfgProps != null;
if(privateCfgProps.isEmpty() || setBrowserProps) {
privateCfgProps.setProperty(JFXProjectProperties.RUN_AS, runAs.getString());
privateCfgProps.setComment(JFXProjectProperties.RUN_AS, new String[]{"# " + NbBundle.getMessage(JFXProjectUtils.class, "COMMENT_run_as_defaults")}, false); // NOI18N
if(setBrowserProps) {
Map<String,String> browserInfo = getDefaultBrowserInfo();
if(browserInfo != null && !browserInfo.isEmpty()) {
for(Map.Entry<String,String> entry : browserInfo.entrySet()) {
privateCfgProps.setProperty(JFXProjectProperties.RUN_IN_BROWSER, entry.getKey());
privateCfgProps.setProperty(JFXProjectProperties.RUN_IN_BROWSER_PATH, entry.getValue());
break;
}
}
}
saveToFile(projDir, privatePath, privateCfgProps);
updated = true;
}
return updated;
}
/**
* @return name and path to default browser
*/
public static Map<String,String> getDefaultBrowserInfo() {
Lookup.Result<ExtWebBrowser> allBrowsers = Lookup.getDefault().lookupResult(ExtWebBrowser.class);
Map<String,String> browserPaths = new HashMap<String, String>();
for(Lookup.Item<ExtWebBrowser> browser : allBrowsers.allItems()) {
String name = browser.getDisplayName();
if(name != null && name.toLowerCase().contains("default")) { // NOI18N
NbProcessDescriptor proc = browser.getInstance().getBrowserExecutable();
String path = proc.getProcessName();
if(JFXProjectProperties.isNonEmpty(path)) {
browserPaths.put(name, path);
}
break;
}
}
return browserPaths;
}
/**
* Returns a copy of existing list of maps, usually used to store application parameters
*
* @param list2Copy
* @return copy of list2Copy
*/
public static List<Map<String,String>> copyList(List<Map<String,String>> list2Copy) {
List<Map<String,String>> list2Return = new ArrayList<Map<String,String>>();
if(list2Copy != null ) {
for (Map<String,String> map : list2Copy) {
list2Return.add(copyMap(map));
}
}
return list2Return;
}
/**
* Returns a copy of existing string map, usually used to store application parameter
* (keys 'name' and 'value')
*
* @param map2Copy
* @return copy of map2Copy
*/
public static Map<String,String> copyMap(Map<String,String> map2Copy) {
Map<String,String> newMap = new HashMap<String,String>();
if(map2Copy != null) {
for(String key : map2Copy.keySet()) {
String value = map2Copy.get(key);
newMap.put(key, value);
}
}
return newMap;
}
/**
* Modifies name so that it can be used as file name,
* i.e., replaces problematic characters.
*
* @param name
* @return modified name usable as file name
*/
public static String makeSafe(@NonNull String name) {
return name.replaceAll("[^a-zA-Z0-9_.-]", "_"); // NOI18N;
}
public static EditableProperties readFromFile(final @NonNull Project project, final @NonNull String relativePath) throws IOException {
final FileObject dirFO = project.getProjectDirectory();
return readFromFile(dirFO, relativePath);
}
public static EditableProperties readFromFile(final @NonNull FileObject dirFO, final @NonNull String relativePath) throws IOException {
assert dirFO.isFolder();
final FileObject propsFO = dirFO.getFileObject(relativePath);
return readFromFile(propsFO);
}
public static EditableProperties readFromFile(final @NonNull FileObject propsFO) throws IOException {
final EditableProperties ep = new EditableProperties(true);
if(propsFO != null) {
assert propsFO.isData();
try {
ProjectManager.mutex().readAccess(new Mutex.ExceptionAction<Void>() {
@Override
public Void run() throws Exception {
final InputStream is = propsFO.getInputStream();
try {
ep.load(is);
} finally {
if (is != null) {
is.close();
}
}
return null;
}
});
} catch (MutexException mux) {
throw (IOException) mux.getException();
}
}
return ep;
}
public static void deleteFile(final @NonNull Project project, final @NonNull String relativePath) throws IOException {
final FileObject propsFO = project.getProjectDirectory().getFileObject(relativePath);
deleteFile(propsFO);
}
public static void deleteFile(final @NonNull FileObject dirFO, final @NonNull String relativePath) throws IOException {
assert dirFO.isFolder();
final FileObject propsFO = dirFO.getFileObject(relativePath);
deleteFile(propsFO);
}
public static void deleteFile(final @NonNull FileObject propsFO) throws IOException {
if(propsFO != null) {
try {
ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() {
@Override
public Void run() throws Exception {
propsFO.delete();
return null;
}
});
} catch (MutexException mux) {
throw (IOException) mux.getException();
}
}
}
public static void saveToFile(final @NonNull Project project, final @NonNull String relativePath, final @NonNull EditableProperties ep) throws IOException {
FileObject dirFO = project.getProjectDirectory();
saveToFile(dirFO, relativePath, ep);
}
public static void saveToFile(final @NonNull FileObject dirFO, final @NonNull String relativePath, final @NonNull EditableProperties ep) throws IOException {
assert dirFO.isFolder();
FileObject f = dirFO.getFileObject(relativePath);
final FileObject propsFO;
if(f == null) {
propsFO = FileUtil.createData(dirFO, relativePath);
assert propsFO != null : "FU.cD must not return null; called on " + dirFO + " + " + relativePath; // #50802 // NOI18N
} else {
propsFO = f;
}
saveToFile(propsFO, ep);
}
public static void saveToFile(final @NonNull FileObject propsFO, final @NonNull EditableProperties ep) throws IOException {
if(propsFO != null) {
assert propsFO.isData();
try {
ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() {
@Override
public Void run() throws Exception {
OutputStream os = null;
FileLock lock = null;
try {
lock = propsFO.lock();
os = propsFO.getOutputStream(lock);
ep.store(os);
} finally {
if (os != null) {
os.close();
}
if (lock != null) {
lock.releaseLock();
}
}
return null;
}
});
} catch (MutexException mux) {
throw (IOException) mux.getException();
}
}
}
}