/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2012 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. * * Contributor(s): * * Portions Copyrighted 2012 Sun Microsystems, Inc. */ package ca.weblite.netbeans.mirah.antproject.base; import ca.weblite.asm.WLMirahCompiler; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.java.project.JavaProjectConstants; import org.netbeans.api.java.project.classpath.ProjectClassPathModifier; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; import org.netbeans.api.project.ProjectUtils; import org.netbeans.api.project.SourceGroup; import org.netbeans.api.project.Sources; import org.netbeans.api.project.ant.AntBuildExtender; import org.netbeans.api.project.libraries.Library; import org.netbeans.api.project.libraries.LibraryManager; import ca.weblite.netbeans.mirah.support.spi.MirahExtenderImplementation; import java.io.PrintWriter; import java.net.URI; import org.netbeans.spi.project.support.ant.GeneratedFilesHelper; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.filesystems.URLMapper; import org.openide.util.EditableProperties; import org.openide.util.Exceptions; import org.openide.util.Mutex; import org.openide.util.MutexException; import org.openide.util.Parameters; /** * Base class for each Ant based mirah extender. This class encapsulates most of * the code common for those types of project. The only thing that implementor * needs to do is return xls file used for mirah-build.xml generation. * * @author Martin Janicek */ public abstract class AbstractMirahExtender implements MirahExtenderImplementation { private static final String EXTENSIBLE_TARGET_NAME = "-pre-pre-compile"; // NOI18N private static final String MIRAH_EXTENSION_ID = "mirah"; // NOI18N private static final String PROJECT_PROPERTIES_PATH = "nbproject/project.properties"; // NOI18N private static final String CN1_LIBRARY_PROPERTIES_PATH = "codenameone_library.properties"; private static final String EXCLUDE_PROPERTY = "build.classes.excludes"; // NOI18N private static final String DISABLE_COMPILE_ON_SAVE = "compile.on.save.unsupported.mirah"; // NOI18N private static final String EXCLUSION_PATTERN = "**/*.mirah"; // NOI18N private static final String MIRAH_BUILD_PATH_PROPERTY = "mirah.build.dir"; private static final String MIRAH_MACROS_JARDIR_PROPERTY = "mirah.macros.jardir"; private static final String VERSION_PROPERTY = "mirah.plugin.version"; private static final int PLUGIN_VERSION=25; private final Project project; protected AbstractMirahExtender(Project project) { this.project = project; } protected abstract URL getMirahBuildXls(); /** * Checks if the project has mirah activated. Please be aware that this method * checks only build script extension, not ClassPath nor excludes * * @return true if the project has modified build-impl.xml with mirah extension */ @Override public boolean isActive() { URL loc = WLMirahCompiler.class.getProtectionDomain().getCodeSource().getLocation(); AntBuildExtender extender = project.getLookup().lookup(AntBuildExtender.class); boolean out = extender != null && extender.getExtension(MIRAH_EXTENSION_ID) != null; return out; } @Override public boolean activate() { boolean out = addClasspath() & addExcludes() & addBuildScript() & addMacrosClasspath()/*& addDisableCompileOnSaveProperty()*/; return out; } @Override public boolean deactivate() { return removeClasspath() & removeExcludes() & removeBuildScript() /*& removeDisableCompileOnSaveProperty()*/; } /** * Add mirah-all.jar to the project ClassPath. */ protected final boolean addClasspath() { try { Sources sources = ProjectUtils.getSources(project); FileObject rootDir = project.getProjectDirectory(); FileObject buildDir = rootDir.getFileObject("build"); if ( buildDir == null ){ buildDir = rootDir.createFolder("build"); } FileObject mirahDir = buildDir.getFileObject("mirah"); if ( mirahDir == null ){ mirahDir = buildDir.createFolder("mirah"); } SourceGroup[] sourceGroups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA); for (SourceGroup sourceGroup : sourceGroups) { if (!sourceGroup.getRootFolder().getName().equals("test")) { //ProjectClassPathModifier.addLibraries(new Library[]{mirahAllLib}, sourceGroup.getRootFolder(), ClassPath.COMPILE); ProjectClassPathModifier.addRoots(new URI[]{mirahDir.toURI()}, sourceGroup.getRootFolder(), ClassPath.COMPILE); } } return true; } catch (IOException ex) { Exceptions.printStackTrace(ex); } catch (UnsupportedOperationException ex) { Exceptions.printStackTrace(ex); } return false; } /** * Inverse operation to {@link #addClasspath()}. * Removes mirah-all.jar from project ClassPath. */ protected final boolean removeClasspath() { if ( true ) return true; Library mirahAllLib = getMirahAllLibrary(); // NOI18N if (mirahAllLib != null) { try { Sources sources = ProjectUtils.getSources(project); SourceGroup[] sourceGroups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA); for (SourceGroup sourceGroup : sourceGroups) { ProjectClassPathModifier.removeLibraries(new Library[]{mirahAllLib}, sourceGroup.getRootFolder(), ClassPath.COMPILE); } return true; } catch (IOException ex) { Exceptions.printStackTrace(ex); } catch (UnsupportedOperationException ex) { Exceptions.printStackTrace(ex); } } return false; } private Library getMirahAllLibrary() { for (Library library : LibraryManager.getDefault().getLibraries()) { List<URL> uriContent = library.getContent("classpath"); // NOI18N try { if (containsClass(uriContent, "ca.weblite.mirah.ant.WLMirahCompiler")) { // NOI18N return library; } } catch (IOException ex) { Exceptions.printStackTrace(ex); } } return null; } private boolean containsClass(List<URL> classPath, String className) throws IOException { Parameters.notNull("classpath", classPath); // NOI18N Parameters.notNull("className", className); // NOI18N List<File> diskFiles = new ArrayList<File>(); for (URL url : classPath) { URL archiveURL = FileUtil.getArchiveFile(url); if (archiveURL != null) { url = archiveURL; } if ("nbinst".equals(url.getProtocol())) { // NOI18N // try to get a file: URL for the nbinst: URL FileObject fo = URLMapper.findFileObject(url); if (fo != null) { URL localURL = URLMapper.findURL(fo, URLMapper.EXTERNAL); if (localURL != null) { url = localURL; } } } FileObject fo = URLMapper.findFileObject(url); if (fo != null) { File diskFile = FileUtil.toFile(fo); if (diskFile != null) { diskFiles.add(diskFile); } } } return containsClass(diskFiles, className); } private boolean containsClass(Collection<File> classpath, String className) throws IOException { Parameters.notNull("classpath", classpath); // NOI18N Parameters.notNull("driverClassName", className); // NOI18N String classFilePath = className.replace('.', '/') + ".class"; // NOI18N for (File file : classpath) { if (file.isFile()) { JarFile jf = new JarFile(file); try { Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = (JarEntry) entries.nextElement(); if (classFilePath.equals(entry.getName())) { return true; } } } finally { jf.close(); } } else { if (new File(file, classFilePath).exists()) { return true; } } } return false; } /** * Add **\/*.mirah to build.classes.excludes. */ protected final boolean addExcludes() { try { EditableProperties props = getEditableProperties(project, PROJECT_PROPERTIES_PATH); String exclude = props.getProperty(EXCLUDE_PROPERTY); //props.setProperty("mirahc.ant.classpath", "foobarfoo"); if (!exclude.contains(EXCLUSION_PATTERN)) { //System.out.println(EXCLUSION_PATTERN+" is not found"); props.setProperty(EXCLUDE_PROPERTY, exclude + "," + EXCLUSION_PATTERN); // NOI18N storeEditableProperties(project, PROJECT_PROPERTIES_PATH, props); } else { //System.out.println("Exclusion pattern "+EXCLUSION_PATTERN+" was found"); } return true; } catch (IOException ex) { Exceptions.printStackTrace(ex); } return false; } /** * Inverse operation to {@link #addExcludes()}. * Remove **\/*.mirah from build.classes.excludes. */ protected final boolean removeExcludes() { try { EditableProperties props = getEditableProperties(project, PROJECT_PROPERTIES_PATH); String exclude = props.getProperty(EXCLUDE_PROPERTY); if (exclude.contains("," + EXCLUSION_PATTERN)) { exclude = exclude.replace("," + EXCLUSION_PATTERN, ""); props.setProperty(EXCLUDE_PROPERTY, exclude); storeEditableProperties(project, PROJECT_PROPERTIES_PATH, props); } return true; } catch (IOException ex) { Exceptions.printStackTrace(ex); } return false; } protected final boolean addMacrosClasspath(){ try { EditableProperties props = getEditableProperties(project, PROJECT_PROPERTIES_PATH); props.setProperty(MIRAH_MACROS_JARDIR_PROPERTY, "lib/mirah/macros"); // NOI18N storeEditableProperties(project, PROJECT_PROPERTIES_PATH, props); FileObject projectFO = project.getProjectDirectory(); FileUtil.createFolder(new File(projectFO.getPath(), "lib/mirah/macros")); return true; } catch (IOException ex) { Exceptions.printStackTrace(ex); } return false; } /** * Wraps javac into the mirahc using imported mirah-build.xml. Adds mirah-build.xml * to the project, modifies build-impl.xml with respect to mirah compiler. This method * has to be call on every project that needs to compile mirah scripts/classes. * * @return true if the mirah extension were successfully applied, false otherwise */ protected final boolean addBuildScript() { try { AntBuildExtender extender = project.getLookup().lookup(AntBuildExtender.class); System.out.println("Step 0"); if (extender != null && extender.getExtensibleTargets().contains(EXTENSIBLE_TARGET_NAME)) { System.out.println("Step 1"); AntBuildExtender.Extension extension = extender.getExtension(MIRAH_EXTENSION_ID); if (extension == null) { System.out.println("Step 2"); FileObject destDirFO = project.getProjectDirectory().getFileObject("nbproject"); // NOI18N FileObject projectFO = project.getProjectDirectory(); try { GeneratedFilesHelper helper = new GeneratedFilesHelper(project.getProjectDirectory()); // Check if this is a codename one project FileObject cn1PropertiesFO = projectFO.getFileObject("codenameone_settings", "properties"); FileObject cn1LibraryPropertiesFO = projectFO.getFileObject("codenameone_library", "properties"); boolean isCodename1Lib = cn1LibraryPropertiesFO != null; boolean isCodename1Proj = cn1PropertiesFO != null; if ( isCodename1Lib ){ addCN1LibProperty(); } if ( isCodename1Lib || isCodename1Proj){ // This is a codename one project // With Codename One projects we don't want to override Javac. // Instead we will do a precompile and add the .class files to the // lib/cls/impl directory so that they will be treated like // a .cn1lib file. FileObject cn1buildFO = destDirFO.getFileObject("mirah-build-cn1", "xml"); OutputStream os = null; if ( cn1buildFO == null ){ os = destDirFO.createAndOpen("mirah-build-cn1.xml"); } else { os = cn1buildFO.getOutputStream(); } InputStream input = AbstractMirahExtender.class.getResourceAsStream("/ca/weblite/netbeans/mirah/antproject/resources/mirah-build-cn1.xml"); try { FileUtil.copy(input, os); cn1buildFO = destDirFO.getFileObject("mirah-build-cn", "xml"); } finally { try { os.close(); } catch ( Exception ex){} try { input.close(); } catch ( Exception ex){} } if ( cn1buildFO != null ){ AntBuildExtender.Extension cn1extension = extender.getExtension("mirah-cn1-build"); if ( cn1extension == null ){ cn1extension = extender.addExtension("mirah-cn1-build", cn1buildFO); cn1extension.addDependency(EXTENSIBLE_TARGET_NAME, "mirah-precompile"); cn1extension.addDependency(EXTENSIBLE_TARGET_NAME, "mirah-precompile-cn1lib"); cn1extension.addDependency("-pre-pre-jar", "mirah-postcompile-cn1lib"); } } } helper.generateBuildScriptFromStylesheet("nbproject/mirah-build.xml", getMirahBuildXls()); FileObject destFileFO = destDirFO.getFileObject("mirah-build", "xml"); // NOI18N extension = extender.addExtension(MIRAH_EXTENSION_ID, destFileFO); extension.addDependency(EXTENSIBLE_TARGET_NAME, "-mirah-init-macrodef-javac"); // NOI18N //extension.addDependency("-init-project", "-mirah-pre-init"); ProjectManager.getDefault().saveProject(project); FileObject buildImplFO = destDirFO.getFileObject("build-impl", "xml"); String contents = buildImplFO.asText(); if ( !contents.contains("mirah-build.xml")){ // Codename One projects don't seem to be regenerating the build-impl.xml file automatically // so we have to use this ugly regex workaround to updating it ourselves. contents = contents.replaceAll("<project [^>]*>", "$0\n <import file=\"mirah-build.xml\"/>"); if ( isCodename1Lib || isCodename1Proj){ contents = contents.replaceAll("<project [^>]*>", "$0\n <import file=\"mirah-build-cn1.xml\"/>"); } contents = contents.replaceAll("(<target depends=\".*?)(\"[^>]* name=\"-pre-pre-compile\">)", "$1,-mirah-init-macrodef-javac$2"); if ( isCodename1Lib || isCodename1Proj){ contents = contents.replaceAll("(<target depends=\".*?)(\"[^>]* name=\"-pre-pre-compile\">)", "$1,mirah-precompile,mirah-precompile-cn1lib$2"); } contents = contents.replaceAll("(<target depends=\".*?)(\"[^>]* name=\"init\"[^>]*>)", "$1,-mirah-pre-init$2"); PrintWriter os = null; try { os = new PrintWriter(buildImplFO.getOutputStream()); os.write(contents); } finally { try { os.close(); } catch ( Exception ex){} } } else if ( !contents.contains("-mirah-pre-init")){ System.out.println("Installing mirah pre init"); contents = contents.replaceAll("(<target depends=\".*?)(\"[^>]* name=\"init\"[^>]*>)", "$1,-mirah-pre-init$2"); PrintWriter os = null; try { os = new PrintWriter(buildImplFO.getOutputStream()); os.write(contents); } finally { try { os.close(); } catch ( Exception ex){} } } return true; } catch (IOException ioe) { Exceptions.printStackTrace(ioe); } } else { // extension is already registered return true; } } } finally { } return false; } /** * Inverse operation to {@link #addBuildScript()}. Removes mirah-build.xml * from the project and reverts changes in build-impl.xml related to mirah * activation. * * @return true if the mirah extension in build scripts were successfully deactivated */ protected final boolean removeBuildScript() { AntBuildExtender extender = project.getLookup().lookup(AntBuildExtender.class); if (extender != null && extender.getExtensibleTargets().contains(EXTENSIBLE_TARGET_NAME)) { AntBuildExtender.Extension extension = extender.getExtension(MIRAH_EXTENSION_ID); if (extension != null) { FileObject destDirFO = project.getProjectDirectory().getFileObject("nbproject"); // NOI18N try { extension.removeDependency(EXTENSIBLE_TARGET_NAME, "-mirah-init-macrodef-javac"); // NOI18N //extension.removeDependency("-init-project", "-mirah-pre-init"); extender.removeExtension(MIRAH_EXTENSION_ID); if (destDirFO != null) { FileObject fileToRemove = destDirFO.getFileObject("mirah-build.xml"); // NOI18N if (fileToRemove != null) { fileToRemove.delete(); } } ProjectManager.getDefault().saveProject(project); FileObject buildImplFO = destDirFO.getFileObject("build-impl", "xml"); String contents = buildImplFO.asText(); if ( contents.contains("mirah-build.xml")){ //contents = contents.replaceAll("<project [^>]*>", "$0\n <import file=\"mirah-build-cn1.xml\"/><import file=\"mirah-build.xml\"/>"); contents = contents.replaceAll("<import file=\"mirah-build-cn1.xml\"/>", ""); contents = contents.replaceAll("<import file=\"mirah-build.xml\"/>", ""); //contents = contents.replaceAll("(<target depends=\".*?)(\"[^>]* name=\"-pre-pre-compile\">)", "$1,mirah-precompile,mirah-precompile-cn1lib,-mirah-init-macrodef-javac$2"); contents = contents.replaceAll(",mirah-precompile,mirah-precompile-cn1lib", ""); contents = contents.replaceAll(",-mirah-init-macrodef-javac", ""); //contents = contents.replaceAll("(<target depends=\".*?)(\"[^>]* name=\"init\"[^>]*>)", "$1,-mirah-pre-init$2"); contents = contents.replaceAll(",-mirah-pre-init", ""); PrintWriter os = null; try { os = new PrintWriter(buildImplFO.getOutputStream()); os.write(contents); } finally { try { os.close(); } catch ( Exception ex){} } } else if ( contents.contains(",-mirah-pre-init")){ contents = contents.replaceAll(",-mirah-pre-init", ""); PrintWriter os = null; try { os = new PrintWriter(buildImplFO.getOutputStream()); os.write(contents); } finally { try { os.close(); } catch ( Exception ex){} } } return true; } catch (IOException ioe) { Exceptions.printStackTrace(ioe); } } else { // extension is not registered return true; } } return false; } /** * Disables compile on save for the project. * * @return true if CoS were disabled, false otherwise */ protected final boolean addDisableCompileOnSaveProperty() { try { EditableProperties props = getEditableProperties(project, PROJECT_PROPERTIES_PATH); props.put(DISABLE_COMPILE_ON_SAVE, "true"); storeEditableProperties(project, PROJECT_PROPERTIES_PATH, props); return true; } catch (IOException ex) { Exceptions.printStackTrace(ex); } return false; } private final boolean addCN1LibProperty(){ try { EditableProperties props = getEditableProperties(project, CN1_LIBRARY_PROPERTIES_PATH); props.put("codename1.is_library", "true"); storeEditableProperties(project, CN1_LIBRARY_PROPERTIES_PATH, props); return true; } catch (IOException ex) { Exceptions.printStackTrace(ex); } return false; } /** * Inverse operation to {@link #addDisableCompileOnSaveProperty()}. * Enabled compile on save for the project. * * @return true if CoS were enabled, false otherwise */ protected final boolean removeDisableCompileOnSaveProperty() { try { EditableProperties props = getEditableProperties(project, PROJECT_PROPERTIES_PATH); props.remove(DISABLE_COMPILE_ON_SAVE); storeEditableProperties(project, PROJECT_PROPERTIES_PATH, props); return true; } catch (IOException ex) { Exceptions.printStackTrace(ex); } return false; } private static EditableProperties getEditableProperties(final Project prj,final String propertiesPath) throws IOException { try { return ProjectManager.mutex().readAccess(new Mutex.ExceptionAction<EditableProperties>() { @Override public EditableProperties run() throws IOException { FileObject propertiesFo = prj.getProjectDirectory().getFileObject(propertiesPath); EditableProperties ep = null; if (propertiesFo!=null) { InputStream is = null; ep = new EditableProperties(false); try { is = propertiesFo.getInputStream(); ep.load(is); } finally { if (is != null) { is.close(); } } } return ep; } }); } catch (MutexException ex) { return null; } } private static void storeEditableProperties(final Project prj, final String propertiesPath, final EditableProperties ep) throws IOException { try { ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() { @Override public Void run() throws IOException { FileObject propertiesFo = prj.getProjectDirectory().getFileObject(propertiesPath); if (propertiesFo!=null) { OutputStream os = null; try { os = propertiesFo.getOutputStream(); ep.store(os); } finally { if (os != null) { os.close(); } } } return null; } }); } catch (MutexException ex) { } } @Override public boolean isCurrent(){ try { EditableProperties props = getEditableProperties(project, PROJECT_PROPERTIES_PATH); int version = 0; if ( props.containsKey(VERSION_PROPERTY) ){ try { version = Integer.parseInt(props.getProperty(VERSION_PROPERTY)); } catch ( Throwable t){} } System.out.println("The current version is "+version); return isActive() && version >= PLUGIN_VERSION; } catch (IOException ex) { throw new RuntimeException(ex); } } @Override public boolean update(){ try { EditableProperties props = getEditableProperties(project, PROJECT_PROPERTIES_PATH); int version = 0; if ( props.containsKey(VERSION_PROPERTY) ){ try { version = Integer.parseInt(props.getProperty(VERSION_PROPERTY)); } catch ( Throwable t){} } if ( !removeExcludes() || !removeBuildScript() || !addExcludes() || !addBuildScript()){ System.out.println("Failed to update"); return false; } if ( version < PLUGIN_VERSION){ props = getEditableProperties(project, PROJECT_PROPERTIES_PATH); props.setProperty(VERSION_PROPERTY, String.valueOf(PLUGIN_VERSION)); storeEditableProperties(project, PROJECT_PROPERTIES_PATH, props); } return true; } catch (IOException ex) { Exceptions.printStackTrace(ex); return false; } } }