/** * Try to load a config XML file from a named path. If the file does not exist, return * NONEXISTENT; or if there is any load error, return null. */ private Document loadXml(String path) { assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess(); assert Thread.holdsLock(modifiedMetadataPaths); FileObject xml = dir.getFileObject(path); if (xml == null || !xml.isData()) { return NONEXISTENT; } try { Document doc = XMLUtil.parse( new InputSource(xml.getInputStream()), false, true, XMLUtil.defaultErrorHandler(), null); return doc; } catch (IOException e) { if (!QUIETLY_SWALLOW_XML_LOAD_ERRORS) { LOG.log(Level.INFO, "Load XML: {0}", xml.getPath()); // NOI18N ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } catch (SAXException e) { if (!QUIETLY_SWALLOW_XML_LOAD_ERRORS) { LOG.log(Level.INFO, "Load XML: {0}", xml.getPath()); // NOI18N ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } return null; }
/** Presents commit message as tooltips. */ @Override public String getToolTipText(MouseEvent e) { if (editorUI == null) return null; int line = getLineFromMouseEvent(e); StringBuilder annotation = new StringBuilder(); if (!elementAnnotations.isEmpty()) { AnnotateLine al = getAnnotateLine(line); if (al != null) { String escapedAuthor = NbBundle.getMessage(AnnotationBar.class, "TT_Annotation"); // NOI18N try { escapedAuthor = XMLUtil.toElementContent(al.getAuthor()); } catch (CharConversionException e1) { Mercurial.LOG.log(Level.INFO, "HG.AB: can not HTML escape: ", al.getAuthor()); // NOI18N } // always return unique string to avoid tooltip sharing on mouse move over same revisions // --> annotation .append("<html><!-- line=") .append(line++) .append(" -->") .append(al.getRevision()) .append(":") .append(al.getId()) .append(" - <b>") .append(escapedAuthor) .append("</b>"); // NOI18N if (al.getDate() != null) { annotation .append(" ") .append( DateFormat.getDateInstance().format(al.getDate())); // NOI18N } if (al.getCommitMessage() != null) { String escaped = null; try { escaped = XMLUtil.toElementContent(al.getCommitMessage()); } catch (CharConversionException e1) { Mercurial.LOG.log( Level.INFO, "HG.AB: can not HTML escape: ", al.getCommitMessage()); // NOI18N } if (escaped != null) { String lined = escaped.replaceAll(System.getProperty("line.separator"), "<br>"); // NOI18N annotation.append("<p>").append(lined); // NOI18N } } } } else { annotation.append(elementAnnotationsSubstitute); } return annotation.toString(); }
static Object load(InputStream is, String name, Handler handler) { try { try { XMLReader reader = XMLUtil.createXMLReader(); reader.setEntityResolver(handler); reader.setContentHandler(handler); reader.parse(new InputSource(is)); return handler.getResult(); } finally { is.close(); } } catch (SAXException ex) { if (System.getProperty("org.netbeans.optionsDialog") != null) { System.out.println("File: " + name); ex.printStackTrace(); } return handler.getResult(); } catch (IOException ex) { if (System.getProperty("org.netbeans.optionsDialog") != null) { System.out.println("File: " + name); ex.printStackTrace(); } return handler.getResult(); } catch (Exception ex) { if (System.getProperty("org.netbeans.optionsDialog") != null) { System.out.println("File: " + name); ex.printStackTrace(); } return handler.getResult(); } }
/** * Get the <code><configuration></code> element of project.xml or the document element of * private.xml. Beneath this point you can load and store configuration fragments. * * @param shared if true, use project.xml, else private.xml * @return the data root */ private Element getConfigurationDataRoot(boolean shared) { assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess(); assert Thread.holdsLock(modifiedMetadataPaths); Document doc = getConfigurationXml(shared); if (shared) { Element project = doc.getDocumentElement(); Element config = XMLUtil.findElement(project, "configuration", PROJECT_NS); // NOI18N assert config != null; return config; } else { return doc.getDocumentElement(); } }
private static void filterProjectXML(FileObject fo, ZipInputStream str, String name) throws IOException { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); FileUtil.copy(str, baos); Document doc = XMLUtil.parse( new InputSource(new ByteArrayInputStream(baos.toByteArray())), false, false, null, null); NodeList nl = doc.getDocumentElement().getElementsByTagName("name"); if (nl != null) { for (int i = 0; i < nl.getLength(); i++) { Element el = (Element) nl.item(i); if (el.getParentNode() != null && "data".equals(el.getParentNode().getNodeName())) { NodeList nl2 = el.getChildNodes(); if (nl2.getLength() > 0) { nl2.item(0).setNodeValue(name); } break; } } } OutputStream out = fo.getOutputStream(); try { XMLUtil.write(doc, out, "UTF-8"); } finally { out.close(); } } catch (Exception ex) { Exceptions.printStackTrace(ex); writeFile(str, fo); } }
public void parse(java.io.Reader src) throws IOException { try { org.xml.sax.XMLReader reader = org.openide.xml.XMLUtil.createXMLReader(false, false); reader.setContentHandler(this); reader.setEntityResolver(this); org.xml.sax.InputSource is = new org.xml.sax.InputSource(src); try { reader.setProperty("http://xml.org/sax/properties/lexical-handler", this); // NOI18N } catch (SAXException sex) { XMLSettingsSupport.err.warning( "Warning: XML parser does not support lexical-handler feature."); // NOI18N } reader.parse(is); } catch (SAXException ex) { IOException ioe = new IOException(); ioe.initCause(ex); throw ioe; } }
/** * Retrieve project.xml or private.xml, loading from disk as needed. private.xml is created as a * skeleton on demand. */ private Document getConfigurationXml(boolean shared) { assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess(); assert Thread.holdsLock(modifiedMetadataPaths); if (!(shared ? projectXmlValid : privateXmlValid)) { String path = shared ? PROJECT_XML_PATH : PRIVATE_XML_PATH; Document _xml = loadXml(path); if (_xml != null && _xml != NONEXISTENT) { if (shared) { projectXml = _xml; } else { privateXml = _xml; } } else if (_xml == NONEXISTENT && !shared) { privateXml = null; } } if (!shared && privateXml == null) { // Missing or broken; create a skeleton. // (projectXml must have been given a valid value when APH was constructed.) privateXml = XMLUtil.createDocument("project-private", PRIVATE_NS, null, null); // NOI18N } // Mark valid even if had parse errors, so we do not try to reparse until corrected: if (shared) { projectXmlValid = true; } else { privateXmlValid = true; } Document xml = shared ? projectXml : privateXml; assert xml != null : "shared=" + shared + " projectXml=" + projectXml + " privateXml=" + privateXml + " projectXmlValid=" + projectXmlValid + " privateXmlValid=" + privateXmlValid; return xml; }
/** @author Alexander Simon */ public final class MakeProjectHelperImpl implements MakeProjectHelper { /** XML namespace of private component of Make projects. */ static final String PRIVATE_NS = "http://www.netbeans.org/ns/project-private/1"; // NOI18N private static final Pattern RELATIVE_SLASH_SEPARATED_PATH = Pattern.compile("[^:/\\\\.][^:/\\\\]*(/[^:/\\\\.][^:/\\\\]*)*"); // NOI18N private static final Logger LOG = Logger.getLogger(MakeProjectHelperImpl.class.getName()); private static RequestProcessor RP; /** Project base directory. */ private final FileObject dir; /** File system project directory belongs to */ private FileSystem fileSystem; /** State object permitting modifications. */ private final ProjectState state; /** Make-based project type factory. */ private final MakeProjectTypeImpl type; /** * Used as a marker that project/privateXml was not found at all, rather than found but malformed. */ private static final Document NONEXISTENT = XMLUtil.createDocument("does-not-exist", null, null, null); // NOI18N /** * Cached project.xml parse (null if not loaded). Access within {@link #modifiedMetadataPaths} * monitor. */ private Document projectXml; private boolean projectXmlValid; /** * Cached private.xml parse (null if not loaded). Access within {@link #modifiedMetadataPaths} * monitor. */ private Document privateXml; private boolean privateXmlValid; /** * Set of relative paths to metadata files which have been modified and which need to be saved. * Also server as a monitor for {@link #projectXml} and {@link #privateXml} accesses; Xerces' DOM * is not thread-safe <em>even for reading<em> (#50198). */ private final Set<String> modifiedMetadataPaths = new HashSet<String>(); private Throwable addedProjectXmlPath; // #155010 /** Registered listeners. Access must be directly synchronized. */ private final List<MakeProjectListener> listeners = new ArrayList<MakeProjectListener>(); /** Listener to XML files; needs to be held as an instance field so it is not GC'd */ private final FileChangeListener fileListener; /** Atomic actions in use to save XML files. */ private final Set<AtomicAction> saveActions = new WeakSet<AtomicAction>(); public static MakeProjectHelperImpl create( FileObject dir, Document projectXml, ProjectState state, MakeProjectTypeImpl type) { FileObject substituted = substituteIfNeed(dir, projectXml); if (substituted != null) { dir = substituted; } return new MakeProjectHelperImpl(dir, projectXml, state, type); } private static FileObject substituteIfNeed(FileObject dir, Document projectXml) { if (!dir.getNameExt().endsWith("shadow")) { // NOI18N return null; } Element root = projectXml.getDocumentElement(); if (root != null) { String mode = getNodeValue(root, MakeProject.REMOTE_MODE); if (RemoteProject.Mode.REMOTE_SOURCES.name().equals(mode)) { String hostUid = getNodeValue(root, MakeProject.REMOTE_FILESYSTEM_HOST); String remotebaseDir = getNodeValue(root, MakeProject.REMOTE_FILESYSTEM_BASE_DIR); if (hostUid != null && remotebaseDir != null) { ExecutionEnvironment env = ExecutionEnvironmentFactory.fromUniqueID(hostUid); FileObject fo = FileSystemProvider.getFileObject(env, remotebaseDir); return fo; } } } return null; } private static String getNodeValue(Element root, String tag) { if (root != null) { NodeList nodeList = root.getElementsByTagName(tag); if (nodeList.getLength() > 0) { Node node = nodeList.item(0); NodeList childNodes = node.getChildNodes(); if (childNodes.getLength() > 0) { return childNodes.item(0).getNodeValue(); } } } return null; } // XXX lock any loaded XML files while the project is modified, to prevent manual editing, // and reload any modified files if the project is unmodified private MakeProjectHelperImpl( FileObject dir, Document projectXml, ProjectState state, MakeProjectTypeImpl type) { this.dir = dir; try { this.fileSystem = dir.getFileSystem(); } catch (FileStateInvalidException ex) { throw new IllegalStateException(ex); } this.state = state; assert state != null; this.type = type; assert type != null; this.projectXml = projectXml; projectXmlValid = true; assert projectXml != null; fileListener = new FileListener(); FileObject resolveFileObject = resolveFileObject(PROJECT_XML_PATH); if (resolveFileObject != null) { resolveFileObject.addFileChangeListener(fileListener); } else { FileSystemProvider.addFileChangeListener(fileListener, fileSystem, PROJECT_XML_PATH); } resolveFileObject = resolveFileObject(PRIVATE_XML_PATH); if (resolveFileObject != null) { resolveFileObject.addFileChangeListener(fileListener); } else { FileSystemProvider.addFileChangeListener(fileListener, fileSystem, PRIVATE_XML_PATH); } } public FileSystem getFileSystem() { return fileSystem; } @Override public FileObject resolveFileObject(String filename) throws IllegalArgumentException { if (filename == null) { throw new NullPointerException("null filename passed to resolveFile"); // NOI18N } FileObject f; if (RELATIVE_SLASH_SEPARATED_PATH.matcher(filename).matches()) { return dir.getFileObject(filename); } else { try { return dir.getFileSystem().findResource(filename); } catch (FileStateInvalidException ex) { Exceptions.printStackTrace(ex); return null; } } } private String resolvePath(String filename) throws IllegalArgumentException { if (filename == null) { throw new NullPointerException("null filename passed to resolveFile"); // NOI18N } String result; if (RELATIVE_SLASH_SEPARATED_PATH.matcher(filename).matches()) { result = dir.getPath() + CndFileUtils.getFileSeparatorChar(fileSystem) + filename; } else { result = filename; } return FileSystemProvider.normalizeAbsolutePath(result, fileSystem); } // //XXX:fullRemote: deprecate and remove // @Override // public File resolveFile(String filename) throws IllegalArgumentException { // Parameters.notNull("filename", filename); // File basedir = new File(dir.getPath()); // if (!basedir.isAbsolute()) { // throw new IllegalArgumentException("nonabsolute basedir passed to resolveFile: " + // basedir); // NOI18N // } // File f; // if (RELATIVE_SLASH_SEPARATED_PATH.matcher(filename).matches()) { // // Shortcut - simple relative path. Potentially faster. // f = new File(basedir, filename.replace('/', File.separatorChar)); // } else { // // All other cases. // String machinePath = filename.replace('/', File.separatorChar).replace('\\', // File.separatorChar); // f = new File(machinePath); // if (!f.isAbsolute()) { // f = new File(basedir, machinePath); // } // assert f.isAbsolute(); // } // return FileUtil.normalizeFile(f); // } /** Get the corresponding Make-based project type factory. */ @Override public NativeProjectType getType() { return type; } /** * Retrieve project.xml or private.xml, loading from disk as needed. private.xml is created as a * skeleton on demand. */ private Document getConfigurationXml(boolean shared) { assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess(); assert Thread.holdsLock(modifiedMetadataPaths); if (!(shared ? projectXmlValid : privateXmlValid)) { String path = shared ? PROJECT_XML_PATH : PRIVATE_XML_PATH; Document _xml = loadXml(path); if (_xml != null && _xml != NONEXISTENT) { if (shared) { projectXml = _xml; } else { privateXml = _xml; } } else if (_xml == NONEXISTENT && !shared) { privateXml = null; } } if (!shared && privateXml == null) { // Missing or broken; create a skeleton. // (projectXml must have been given a valid value when APH was constructed.) privateXml = XMLUtil.createDocument("project-private", PRIVATE_NS, null, null); // NOI18N } // Mark valid even if had parse errors, so we do not try to reparse until corrected: if (shared) { projectXmlValid = true; } else { privateXmlValid = true; } Document xml = shared ? projectXml : privateXml; assert xml != null : "shared=" + shared + " projectXml=" + projectXml + " privateXml=" + privateXml + " projectXmlValid=" + projectXmlValid + " privateXmlValid=" + privateXmlValid; return xml; } /** If true, do not report XML load errors. For use only by unit tests. */ static boolean QUIETLY_SWALLOW_XML_LOAD_ERRORS = false; /** * Try to load a config XML file from a named path. If the file does not exist, return * NONEXISTENT; or if there is any load error, return null. */ private Document loadXml(String path) { assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess(); assert Thread.holdsLock(modifiedMetadataPaths); FileObject xml = dir.getFileObject(path); if (xml == null || !xml.isData()) { return NONEXISTENT; } try { Document doc = XMLUtil.parse( new InputSource(xml.getInputStream()), false, true, XMLUtil.defaultErrorHandler(), null); return doc; } catch (IOException e) { if (!QUIETLY_SWALLOW_XML_LOAD_ERRORS) { LOG.log(Level.INFO, "Load XML: {0}", xml.getPath()); // NOI18N ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } catch (SAXException e) { if (!QUIETLY_SWALLOW_XML_LOAD_ERRORS) { LOG.log(Level.INFO, "Load XML: {0}", xml.getPath()); // NOI18N ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } return null; } private void runSaveAA(AtomicAction action) throws IOException { synchronized (saveActions) { saveActions.add(action); } dir.getFileSystem().runAtomicAction(action); } private byte[] convertLineSeparator(ByteArrayOutputStream in, final String path) { return convertLineSeparator(in, dir.getFileObject(path), dir); } public static byte[] convertLineSeparator( ByteArrayOutputStream in, FileObject fo, FileObject dir) { String lineSeparator = new LineSeparatorDetector(fo, dir).getInitialSeparator(); byte[] data = in.toByteArray(); try { BufferedReader reader = new BufferedReader( new InputStreamReader(new ByteArrayInputStream(data), "UTF-8")); // NOI18N ByteArrayOutputStream baos = new ByteArrayOutputStream(); while (true) { String line = reader.readLine(); if (line == null) { break; } baos.write(line.getBytes("UTF-8")); // NOI18N baos.write(lineSeparator.getBytes("UTF-8")); // NOI18N } reader.close(); baos.close(); data = baos.toByteArray(); } catch (IOException ex) { } return data; } /** Save an XML config file to a named path. If the file does not yet exist, it is created. */ private FileLock saveXml(final Document doc, final String path) throws IOException { assert ProjectManager.mutex().isWriteAccess(); assert Thread.holdsLock(modifiedMetadataPaths); final FileLock[] _lock = new FileLock[1]; runSaveAA( new AtomicAction() { @Override public void run() throws IOException { // Keep a copy of xml *while holding modifiedMetadataPaths monitor*. ByteArrayOutputStream baos = new ByteArrayOutputStream(); XMLUtil.write(doc, baos, "UTF-8"); // NOI18N final byte[] data = convertLineSeparator(baos, path); final FileObject xml = FileUtil.createData(dir, path); try { _lock[0] = xml.lock(); // unlocked by {@link #save} OutputStream os = SmartOutputStream.getSmartOutputStream(xml, _lock[0]); try { os.write(data); } finally { os.close(); } } catch (UserQuestionException uqe) { // #46089 ErrorManager.getDefault().notify(uqe); // Revert the save. if (path.equals(PROJECT_XML_PATH)) { synchronized (modifiedMetadataPaths) { projectXmlValid = false; } } else { assert path.equals(PRIVATE_XML_PATH) : path; synchronized (modifiedMetadataPaths) { privateXmlValid = false; } } fireExternalChange(path); } } }); return _lock[0]; } /** * Get the <code><configuration></code> element of project.xml or the document element of * private.xml. Beneath this point you can load and store configuration fragments. * * @param shared if true, use project.xml, else private.xml * @return the data root */ private Element getConfigurationDataRoot(boolean shared) { assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess(); assert Thread.holdsLock(modifiedMetadataPaths); Document doc = getConfigurationXml(shared); if (shared) { Element project = doc.getDocumentElement(); Element config = XMLUtil.findElement(project, "configuration", PROJECT_NS); // NOI18N assert config != null; return config; } else { return doc.getDocumentElement(); } } /** * Add a listener to changes in the project configuration. * * <p>Thread-safe. * * @param listener a listener to add */ @Override public void addMakeProjectListener(MakeProjectListener listener) { synchronized (listeners) { listeners.add(listener); } } /** * Remove a listener to changes in the project configuration. * * <p>Thread-safe. * * @param listener a listener to remove */ @Override public void removeMakeProjectListener(MakeProjectListener listener) { synchronized (listeners) { listeners.remove(listener); } } /** * Fire a change of external provenance to all listeners. When run under read or write access to * <code>ProjectManager.mutex()</code> property change is fired synchronously, otherwise fire * asynchronously under acquired read lock. * * @param path path to the changed file (XML or properties) */ void fireExternalChange(final String path) { final Mutex.Action<Void> action = new ActionImpl(this, path); if (ProjectManager.mutex().isWriteAccess()) { // Run it right now. postReadRequest would be too late. ProjectManager.mutex().readAccess(action); } else if (ProjectManager.mutex().isReadAccess()) { // Run immediately also. No need to switch to read access. action.run(); } else { // Not safe to acquire a new lock, so run later in read access. rp().post(new RunnableImpl(action)); } } private static synchronized RequestProcessor rp() { if (RP == null) { RP = new RequestProcessor("MakeProjectHelper.RP"); // NOI18N } return RP; } /** * Fire a change to all listeners. Must be called from write access; enters read access while * firing. * * @param path path to the changed file (XML or properties) * @param expected true if the result of an API-initiated change, false if from external causes */ private void fireChange(String path, boolean expected) { assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess(); final MakeProjectListener[] _listeners; synchronized (listeners) { if (listeners.isEmpty()) { return; } _listeners = listeners.toArray(new MakeProjectListener[listeners.size()]); } final MakeProjectEvent ev = new MakeProjectEvent(this, path, expected); final boolean xml = path.equals(PROJECT_XML_PATH) || path.equals(PRIVATE_XML_PATH); ProjectManager.mutex() .readAccess( new Mutex.Action<Void>() { @Override public Void run() { for (MakeProjectListener l : _listeners) { try { if (xml) { l.configurationXmlChanged(ev); } else { l.propertiesChanged(ev); } } catch (RuntimeException e) { // Don't prevent other listeners from being notified. ErrorManager.getDefault().notify(e); } } return null; } }); } /** Call when explicitly modifying some piece of metadata. */ private void modifying(String path) { assert ProjectManager.mutex().isWriteAccess(); state.markModified(); addModifiedMetadataPath(path); fireChange(path, true); } private void addModifiedMetadataPath(String path) { synchronized (modifiedMetadataPaths) { boolean added = modifiedMetadataPaths.add(path); if (added && path.equals(PROJECT_XML_PATH)) { addedProjectXmlPath = new Throwable(); } } } /** * Get the top-level project directory. * * @return the project directory beneath which everything in the project lies */ @Override public FileObject getProjectDirectory() { return dir; } /** * Notification that this project has been deleted. * * @see org.netbeans.spi.project.ProjectState#notifyDeleted * @since 1.8 */ @Override public void notifyDeleted() { state.notifyDeleted(); } /** * Mark this project as being modified without actually changing anything in it. Should only be * called from {@link ProjectGenerator#createProject}. */ public void markModified() { assert ProjectManager.mutex().isWriteAccess(); state.markModified(); // To make sure projectXmlSaved is called: addModifiedMetadataPath(PROJECT_XML_PATH); } /** * Check whether this project is currently modified including modifications to <code>project.xml * </code>. Access from GeneratedFilesHelper. */ void ensureProjectXmlUnmodified(String msg, boolean doSave) { assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess(); synchronized (modifiedMetadataPaths) { if (modifiedMetadataPaths.contains(PROJECT_XML_PATH)) { IllegalStateException ise = new IllegalStateException(msg); if (addedProjectXmlPath != null) { ise.initCause(addedProjectXmlPath); } LOG.log(Level.INFO, null, ise); if (doSave) { try { save(); } catch (IOException x) { LOG.log(Level.INFO, null, x); } } } } } /** * Save all cached project metadata. If <code>project.xml</code> was one of the modified files, * then {@link AntBasedProjectType#projectXmlSaved} is called, presumably creating <code> * build-impl.xml</code> and/or <code>build.xml</code>. */ public void save() throws IOException { assert ProjectManager.mutex().isWriteAccess(); if (!getProjectDirectory().isValid()) { // ProjectManager.saveProject() is called when project is deleted externally.. return; } Set<FileLock> locks = new HashSet<FileLock>(); try { synchronized (modifiedMetadataPaths) { assert !modifiedMetadataPaths.isEmpty(); Set<String> toBeCleared = new HashSet<String>(); try { for (String path : new TreeSet<String>(modifiedMetadataPaths)) { try { if (path.equals(PROJECT_XML_PATH)) { assert projectXml != null; locks.add(saveXml(projectXml, path)); } else if (path.equals(PRIVATE_XML_PATH)) { assert privateXml != null; locks.add(saveXml(privateXml, path)); } } catch (FileAlreadyLockedException x) { // #155037 LOG.log(Level.INFO, null, x); } // As metadata files are saved, take them off the modified list. toBeCleared.add(path); } } finally { modifiedMetadataPaths.removeAll(toBeCleared); LOG.log( Level.FINE, "saved {0} and have left {1}", new Object[] {toBeCleared, modifiedMetadataPaths}); } } } finally { // #57791: release locks outside synchronized block. locks.remove(null); for (FileLock lock : locks) { lock.releaseLock(); } } } /** * Get the primary configuration data for this project. The returned element will be named * according to {@link AntBasedProjectType#getPrimaryConfigurationDataElementName} and {@link * AntBasedProjectType#getPrimaryConfigurationDataElementNamespace}. The project may read this * document fragment to get custom information from <code>nbproject/project.xml</code> and <code> * nbproject/private/private.xml</code>. The fragment will have no parent node and while it may be * modified, you must use {@link #putPrimaryConfigurationData} to store any changes. * * @param shared if true, refers to <code>project.xml</code>, else refers to <code>private.xml * </code> * @return the configuration data that is available */ @Override public Element getPrimaryConfigurationData(final boolean shared) { final String name = type.getPrimaryConfigurationDataElementName(shared); assert name.indexOf(':') == -1; final String namespace = type.getPrimaryConfigurationDataElementNamespace(shared); assert namespace != null && namespace.length() > 0; return ProjectManager.mutex() .readAccess( new Mutex.Action<Element>() { @Override public Element run() { synchronized (modifiedMetadataPaths) { Element el = getConfigurationFragment(name, namespace, shared); if (el != null) { return el; } else { // No such data, corrupt file. return cloneSafely( getConfigurationXml(shared).createElementNS(namespace, name)); } } } }); } /** * Store the primary configuration data for this project. The supplied element must be named * according to {@link AntBasedProjectType#getPrimaryConfigurationDataElementName} and {@link * AntBasedProjectType#getPrimaryConfigurationDataElementNamespace}. The project may save this * document fragment to set custom information in <code>nbproject/project.xml</code> and <code> * nbproject/private/private.xml</code>. The fragment will be cloned and so further modifications * will have no effect. * * <p>Acquires write access from {@link ProjectManager#mutex}. However, you are well advised to * explicitly enclose a <em>complete</em> operation within write access, starting with {@link * #getPrimaryConfigurationData}, to prevent race conditions. * * @param data the desired new configuration data * @param shared if true, refers to <code>project.xml</code>, else refers to <code>private.xml * </code> * @throws IllegalArgumentException if the element is not correctly named */ @Override public void putPrimaryConfigurationData(Element data, boolean shared) throws IllegalArgumentException { String name = type.getPrimaryConfigurationDataElementName(shared); assert name.indexOf(':') == -1; String namespace = type.getPrimaryConfigurationDataElementNamespace(shared); assert namespace != null && namespace.length() > 0; if (!name.equals(data.getLocalName()) || !namespace.equals(data.getNamespaceURI())) { throw new IllegalArgumentException( "Wrong name/namespace: expected {" + namespace + "}" + name + " but was {" + data.getNamespaceURI() + "}" + data.getLocalName()); // NOI18N } putConfigurationFragment(data, shared); } private final class FileListener implements FileChangeListener { public FileListener() {} private void change(FileEvent fe) { synchronized (saveActions) { for (AtomicAction a : saveActions) { if (fe.firedFrom(a)) { return; } } } String path; FileObject f = fe.getFile(); synchronized (modifiedMetadataPaths) { if (f.equals(resolveFileObject(PROJECT_XML_PATH))) { if (modifiedMetadataPaths.contains(PROJECT_XML_PATH)) { // #68872: don't do anything if the given file has non-saved changes: return; } path = PROJECT_XML_PATH; projectXmlValid = false; } else if (f.equals(resolveFileObject(PRIVATE_XML_PATH))) { if (modifiedMetadataPaths.contains(PRIVATE_XML_PATH)) { // #68872: don't do anything if the given file has non-saved changes: return; } path = PRIVATE_XML_PATH; privateXmlValid = false; } else { LOG.log( Level.WARNING, "#184132: unexpected file change in {0}; possibly deleted project?", f); return; } } fireExternalChange(path); } @Override public void fileFolderCreated(FileEvent fe) { change(fe); } @Override public void fileDataCreated(FileEvent fe) { change(fe); } @Override public void fileChanged(FileEvent fe) { change(fe); } @Override public void fileDeleted(FileEvent fe) { change(fe); } @Override public void fileRenamed(FileRenameEvent fe) { change(fe); } @Override public void fileAttributeChanged(FileAttributeEvent fe) { // ignore } } /** * Get a piece of the configuration subtree by name. * * @param elementName the simple XML element name expected * @param namespace the XML namespace expected * @param shared to use project.xml vs. private.xml * @return (a clone of) the named configuration fragment, or null if it does not exist */ Element getConfigurationFragment( final String elementName, final String namespace, final boolean shared) { return ProjectManager.mutex() .readAccess( new Mutex.Action<Element>() { @Override public Element run() { synchronized (modifiedMetadataPaths) { Element root = getConfigurationDataRoot(shared); Element data = XMLUtil.findElement(root, elementName, namespace); if (data != null) { return cloneSafely(data); } else { return null; } } } }); } private static final DocumentBuilder db; static { try { db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new AssertionError(e); } } private static Element cloneSafely(Element el) { // #50198: for thread safety, use a separate document. // Using XMLUtil.createDocument is much too slow. synchronized (db) { Document dummy = db.newDocument(); return (Element) dummy.importNode(el, true); } } /** * Store a piece of the configuration subtree by name. * * @param fragment a piece of the subtree to store (overwrite or add) * @param shared to use project.xml vs. private.xml */ void putConfigurationFragment(final Element fragment, final boolean shared) { ProjectManager.mutex() .writeAccess( new Mutex.Action<Void>() { @Override public Void run() { synchronized (modifiedMetadataPaths) { Element root = getConfigurationDataRoot(shared); Element existing = XMLUtil.findElement( root, fragment.getLocalName(), fragment.getNamespaceURI()); // XXX first compare to existing and return if the same if (existing != null) { root.removeChild(existing); } // the children are alphabetize: find correct place to insert new node Node ref = null; NodeList list = root.getChildNodes(); for (int i = 0; i < list.getLength(); i++) { Node node = list.item(i); if (node.getNodeType() != Node.ELEMENT_NODE) { continue; } int comparison = node.getNodeName().compareTo(fragment.getNodeName()); if (comparison == 0) { comparison = node.getNamespaceURI().compareTo(fragment.getNamespaceURI()); } if (comparison > 0) { ref = node; break; } } root.insertBefore(root.getOwnerDocument().importNode(fragment, true), ref); modifying(shared ? PROJECT_XML_PATH : PRIVATE_XML_PATH); } return null; } }); } /** * Remove a piece of the configuration subtree by name. * * @param elementName the simple XML element name expected * @param namespace the XML namespace expected * @param shared to use project.xml vs. private.xml * @return true if anything was actually removed */ boolean removeConfigurationFragment( final String elementName, final String namespace, final boolean shared) { return ProjectManager.mutex() .writeAccess( new Mutex.Action<Boolean>() { @Override public Boolean run() { synchronized (modifiedMetadataPaths) { Element root = getConfigurationDataRoot(shared); Element data = XMLUtil.findElement(root, elementName, namespace); if (data != null) { root.removeChild(data); modifying(shared ? PROJECT_XML_PATH : PRIVATE_XML_PATH); return true; } else { return false; } } } }); } /** * Create an object permitting this project to store auxiliary configuration. Would be placed into * the project's lookup. * * @return an auxiliary configuration provider object suitable for the project lookup */ @Override public AuxiliaryConfiguration createAuxiliaryConfiguration() { return new ExtensibleMetadataProviderImpl(this); } /** * Create an object permitting this project to expose a cache directory. Would be placed into the * project's lookup. * * @return a cache directory provider object suitable for the project lookup */ @Override public CacheDirectoryProvider createCacheDirectoryProvider() { return new ExtensibleMetadataProviderImpl(this); } /** * Create an implementation of the file sharability query. You may specify a list of source roots * to include that should be considered sharable, as well as a list of build directories that * should not be considered sharable. * * <p>The project directory itself is automatically included in the list of sharable directories * so you need not explicitly specify it. Similarly, the <code>nbproject/private</code> * subdirectory is automatically excluded from VCS, so you do not need to explicitly specify it. * * <p>Any file (or directory) mentioned (explicitly or implicity) in the source directory list but * not in any of the build directory lists, and not containing any build directories inside it, * will be given as sharable. If a directory itself is sharable but some directory inside it is * not, it will be given as mixed. A file or directory inside some build directory will be listed * as not sharable. A file or directory matching neither the source list nor the build directory * list will be treated as of unknown status, but in practice such a file should never have been * passed to this implementation anyway - {@link org.netbeans.api.queries.SharabilityQuery} will * normally only call an implementation in project lookup if the file is owned by that project. * * <p>Each entry in either list should be a string evaluated first for Ant property escapes (if * any), then treated as a file path relative to the project directory (or it may be absolute). * * <p>It is permitted, and harmless, to include items that overlap others. For example, you can * have both a directory and one of its children in the include list. * * <p>Whether or not you use this method, all files named <code>*-private.properties</code> * outside the project are marked unsharable, as are such files inside the project if currently * referenced as project libraries. (See {@link #getProjectLibrariesPropertyProvider}.) <div * class="nonnormative"> * * <p>Typical usage would be: * * <pre> * helper.createSharabilityQuery(helper.getStandardPropertyEvaluator(), * new String[] {"${src.dir}", "${test.src.dir}"}, * new String[] {"${build.dir}", "${dist.dir}"}) * </pre> * * <p>A quick rule of thumb is that the include list should contain any source directories which * <em>might</em> reside outside the project directory; and the exclude list should contain any * directories which you would want to add to a <samp>.cvsignore</samp> file if using CVS (for * example). * * <p>Note that in this case <samp>${src.dir}</samp> and <samp>${test.src.dir}</samp> may be * relative paths inside the project directory; relative paths pointing outside of the project * directory; or absolute paths (generally outside of the project directory). If they refer to * locations inside the project directory, including them does nothing but is harmless - since the * project directory itself is always treated as sharable. If they refer to external locations, * you will need to also make sure that {@link org.netbeans.api.project.FileOwnerQuery} actually * maps files in those directories to this project, or else {@link * org.netbeans.api.queries.SharabilityQuery} will never find this implementation in your project * lookup and may return <code>UNKNOWN</code>. </div> * * @param eval a property evaluator to interpret paths with * @param sourceRoots a list of additional paths to treat as sharable * @param buildDirectories a list of paths to treat as not sharable * @return a sharability query implementation suitable for the project lookup * @see Project#getLookup */ @Override public SharabilityQueryImplementation2 createSharabilityQuery( String[] sourceRoots, String[] buildDirectories) { String[] includes = new String[sourceRoots.length + 1]; System.arraycopy(sourceRoots, 0, includes, 0, sourceRoots.length); includes[sourceRoots.length] = ""; // NOI18N String[] excludes = new String[buildDirectories.length + 1]; System.arraycopy(buildDirectories, 0, excludes, 0, buildDirectories.length); excludes[buildDirectories.length] = "nbproject/private"; // NOI18N return new SharabilityQueryImpl(this, includes, excludes); } @Override public String toString() { return "MakeProjectHelper[" + getProjectDirectory() + "]"; // NOI18N } private static class RunnableImpl implements Runnable { private final Action<Void> action; public RunnableImpl(Action<Void> action) { this.action = action; } @Override public void run() { ProjectManager.mutex().readAccess(action); } } private static class ActionImpl implements Action<Void> { private final String path; private MakeProjectHelperImpl helper; public ActionImpl(MakeProjectHelperImpl helper, String path) { this.path = path; this.helper = helper; } @Override public Void run() { helper.fireChange(path, false); helper = null; return null; } } private static final class ExtensibleMetadataProviderImpl implements AuxiliaryConfiguration, CacheDirectoryProvider { /** Relative path from project directory to the required private cache directory. */ private static final String CACHE_PATH = "nbproject/private"; // NOI18N private final MakeProjectHelperImpl helper; ExtensibleMetadataProviderImpl(MakeProjectHelperImpl helper) { this.helper = helper; } @Override public FileObject getCacheDirectory() throws IOException { return FileUtil.createFolder(helper.getProjectDirectory(), CACHE_PATH); } @Override public Element getConfigurationFragment(String elementName, String namespace, boolean shared) { if (elementName == null || elementName.indexOf(':') != -1 || namespace == null) { throw new IllegalArgumentException("Illegal elementName and/or namespace"); // NOI18N } return helper.getConfigurationFragment(elementName, namespace, shared); } @Override public void putConfigurationFragment(Element fragment, boolean shared) throws IllegalArgumentException { if (fragment.getNamespaceURI() == null || fragment.getNamespaceURI().length() == 0) { throw new IllegalArgumentException("Illegal elementName and/or namespace"); // NOI18N } if (fragment .getLocalName() .equals(helper.getType().getPrimaryConfigurationDataElementName(shared)) && fragment .getNamespaceURI() .equals(helper.getType().getPrimaryConfigurationDataElementNamespace(shared))) { throw new IllegalArgumentException( "elementName + namespace reserved for project's primary configuration data"); // NOI18N } helper.putConfigurationFragment(fragment, shared); } @Override public boolean removeConfigurationFragment(String elementName, String namespace, boolean shared) throws IllegalArgumentException { if (elementName == null || elementName.indexOf(':') != -1 || namespace == null) { throw new IllegalArgumentException("Illegal elementName and/or namespace"); // NOI18N } if (elementName.equals(helper.getType().getPrimaryConfigurationDataElementName(shared)) && namespace.equals( helper.getType().getPrimaryConfigurationDataElementNamespace(shared))) { throw new IllegalArgumentException( "elementName + namespace reserved for project's primary configuration data"); // NOI18N } return helper.removeConfigurationFragment(elementName, namespace, shared); } } private static final class SharabilityQueryImpl implements SharabilityQueryImplementation2, PropertyChangeListener, MakeProjectListener { private final MakeProjectHelperImpl h; private final String[] includes; private final String[] excludes; /** Absolute paths of directories or files to treat as sharable (except for the excludes). */ private String[] includePaths; /** Absolute paths of directories or files to treat as not sharable. */ private String[] excludePaths; SharabilityQueryImpl(MakeProjectHelperImpl h, String[] includes, String[] excludes) { this.h = h; this.includes = includes; this.excludes = excludes; computeFiles(); h.addMakeProjectListener(this); } /** Compute the absolute paths which are and are not sharable. */ private void computeFiles() { String[] _includePaths = computeFrom(includes, false); String[] _excludePaths = computeFrom(excludes, true); synchronized (this) { includePaths = _includePaths; excludePaths = _excludePaths; } } /** Compute a list of absolute paths based on some abstract names. */ private String[] computeFrom(String[] list, boolean excludeProjectLibraryPrivate) { List<String> result = new ArrayList<String>(list.length); for (String val : list) { result.add(h.resolvePath(val)); } // XXX should remove overlaps somehow return result.toArray(new String[result.size()]); } @Override public Sharability getSharability(URI file) { String path = file.getPath(); synchronized (this) { if (contains(path, excludePaths, false)) { return Sharability.NOT_SHARABLE; } return contains(path, includePaths, false) ? (contains(path, excludePaths, true) ? Sharability.MIXED : Sharability.SHARABLE) : Sharability.UNKNOWN; } } /** * Check whether a file path matches something in the supplied list. * * @param a file path to test * @param list a list of file paths * @param reverse if true, check if the file is an ancestor of some item; if false, check if * some item is an ancestor of the file * @return true if the file matches some item */ private static boolean contains(String path, String[] list, boolean reverse) { for (String s : list) { if (path.equals(s)) { return true; } else { if (reverse ? s.startsWith(path + File.separatorChar) : path.startsWith(s + File.separatorChar)) { return true; } } } return false; } @Override public void propertyChange(PropertyChangeEvent evt) { computeFiles(); } @Override public void configurationXmlChanged(MakeProjectEvent ev) { computeFiles(); } @Override public void propertiesChanged(MakeProjectEvent ev) {} } }
public static DotClassPath parse(File dotClasspath, List<Link> links) throws IOException { Document dotClasspathXml; try { dotClasspathXml = XMLUtil.parse( new InputSource(Utilities.toURI(dotClasspath).toString()), false, true, XMLUtil.defaultErrorHandler(), null); } catch (SAXException e) { IOException ioe = (IOException) new IOException(dotClasspath + ": " + e.toString()).initCause(e); // NOI18N throw ioe; } Element classpathEl = dotClasspathXml.getDocumentElement(); if (!"classpath".equals(classpathEl.getLocalName())) { // NOI18N return empty(); } List<Element> classpathEntryEls; try { classpathEntryEls = XMLUtil.findSubElements(classpathEl); } catch (IllegalArgumentException x) { throw new IOException(x); } if (classpathEntryEls == null) { return empty(); } // accessrules are ignored as they are not supported in NB anyway, eg: /* <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"> <accessrules> <accessrule kind="accessible" pattern="com/sun/management/*"/> <accessrule kind="nonaccessible" pattern="com/sun/**"/> </accessrules> </classpathentry> */ List<DotClassPathEntry> classpath = new ArrayList<DotClassPathEntry>(); List<DotClassPathEntry> sources = new ArrayList<DotClassPathEntry>(); DotClassPathEntry output = null; DotClassPathEntry jre = null; for (Element classpathEntry : classpathEntryEls) { Map<String, String> props = new HashMap<String, String>(); NamedNodeMap attrs = classpathEntry.getAttributes(); String linkName = null; for (int i = 0; i < attrs.getLength(); i++) { Node n = attrs.item(i); String key = n.getNodeName(); String value = classpathEntry.getAttribute(n.getNodeName()); if (DotClassPathEntry.ATTRIBUTE_PATH.equals(key)) { String resolvedLink = resolveLink(value, links); if (resolvedLink != null) { linkName = value; value = resolvedLink; } } props.put(key, value); } Element entryAttrs = XMLUtil.findElement(classpathEntry, "attributes", null); // NOI18N if (entryAttrs != null) { /* <classpathentry kind="lib" path="/home/dev/hibernate-annotations-3.3.1.GA/lib/hibernate-commons-annotations.jar" sourcepath="/home/dev/hibernate-annotations-3.3.1.GA/src"> <attributes> <attribute name="javadoc_location" value="file:/home/dev/hibernate-annotations-3.3.1.GA/doc/api/"/> </attributes> </classpathentry> */ List<Element> attrsList = XMLUtil.findSubElements(entryAttrs); if (attrsList != null) { for (Element e : attrsList) { props.put(e.getAttribute("name"), e.getAttribute("value")); // NOI18N } } } DotClassPathEntry entry = new DotClassPathEntry(props, linkName); if (entry.getKind() == DotClassPathEntry.Kind.SOURCE) { sources.add(entry); } else if (entry.getKind() == DotClassPathEntry.Kind.OUTPUT) { assert output == null : "there should be always just one default output"; // NOI18N output = entry; } else if (entry.getKind() == DotClassPathEntry.Kind.CONTAINER && entry.getRawPath().startsWith(Workspace.DEFAULT_JRE_CONTAINER)) { jre = entry; } else { classpath.add(entry); } } return new DotClassPath(classpath, sources, output, jre); }