1 19 20 package org.netbeans.modules.ruby.rubyproject; 21 22 import java.beans.PropertyChangeEvent ; 23 import java.beans.PropertyChangeListener ; 24 import java.beans.PropertyChangeSupport ; 25 import java.io.File ; 26 import java.net.MalformedURLException ; 27 import java.net.URL ; 28 import java.net.URI ; 29 import java.util.Arrays ; 30 import java.util.ArrayList ; 31 import java.util.Collections ; 32 import java.util.Iterator ; 33 import java.util.List ; 34 import java.util.Map ; 35 import java.util.HashMap ; 36 import java.text.MessageFormat ; 37 import org.openide.ErrorManager; 38 import org.openide.filesystems.FileObject; 39 import org.openide.filesystems.FileUtil; 40 import org.openide.util.WeakListeners; 41 import org.openide.util.Mutex; 42 import org.openide.util.NbBundle; 43 import org.w3c.dom.Element ; 44 import org.w3c.dom.NodeList ; 45 import org.w3c.dom.Document ; 46 import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectHelper; 47 import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectEvent; 48 import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectListener; 49 import org.netbeans.modules.ruby.spi.project.support.rake.EditableProperties; 50 import org.netbeans.modules.ruby.spi.project.support.rake.PropertyEvaluator; 51 import org.netbeans.modules.ruby.spi.project.support.rake.ReferenceHelper; 52 import org.netbeans.api.project.ProjectManager; 53 54 59 public final class SourceRoots { 60 61 public static final String PROP_ROOT_PROPERTIES = "rootProperties"; public static final String PROP_ROOTS = "roots"; 64 public static final String DEFAULT_SOURCE_LABEL = NbBundle.getMessage(SourceRoots.class, "NAME_src.dir"); 65 public static final String DEFAULT_TEST_LABEL = NbBundle.getMessage(SourceRoots.class, "NAME_test.src.dir"); 66 67 private final UpdateHelper helper; 68 private final PropertyEvaluator evaluator; 69 private final ReferenceHelper refHelper; 70 private final String elementName; 71 private final String newRootNameTemplate; 72 private List <String > sourceRootProperties; 73 private List <String > sourceRootNames; 74 private List <FileObject> sourceRoots; 75 private List <URL > sourceRootURLs; 76 private final PropertyChangeSupport support; 77 private final ProjectMetadataListener listener; 78 private final boolean isTest; 79 private final File projectDir; 80 81 88 SourceRoots (UpdateHelper helper, PropertyEvaluator evaluator, ReferenceHelper refHelper, String elementName, boolean isTest, String newRootNameTemplate) { 89 assert helper != null && evaluator != null && refHelper != null && elementName != null && newRootNameTemplate != null; 90 this.helper = helper; 91 this.evaluator = evaluator; 92 this.refHelper = refHelper; 93 this.elementName = elementName; 94 this.isTest = isTest; 95 this.newRootNameTemplate = newRootNameTemplate; 96 this.projectDir = FileUtil.toFile(this.helper.getRakeProjectHelper().getProjectDirectory()); 97 this.support = new PropertyChangeSupport (this); 98 this.listener = new ProjectMetadataListener(); 99 this.evaluator.addPropertyChangeListener (WeakListeners.propertyChange(this.listener,this.evaluator)); 100 this.helper.getRakeProjectHelper().addRakeProjectListener ((RakeProjectListener)WeakListeners.create(RakeProjectListener.class, this.listener,this.helper)); 101 } 102 103 104 110 public String [] getRootNames () { 111 return ProjectManager.mutex().readAccess(new Mutex.Action<String []>() { 112 public String [] run() { 113 synchronized (SourceRoots.this) { 114 if (sourceRootNames == null) { 115 readProjectMetadata(); 116 } 117 } 118 return sourceRootNames.toArray (new String [sourceRootNames.size()]); 119 } 120 }); 121 } 122 123 127 public String [] getRootProperties () { 128 return ProjectManager.mutex().readAccess(new Mutex.Action<String []>() { 129 public String [] run() { 130 synchronized (SourceRoots.this) { 131 if (sourceRootProperties == null) { 132 readProjectMetadata(); 133 } 134 } 135 return sourceRootProperties.toArray (new String [sourceRootProperties.size()]); 136 } 137 }); 138 } 139 140 144 public FileObject[] getRoots () { 145 return ProjectManager.mutex().readAccess(new Mutex.Action<FileObject[]>() { 146 public FileObject[] run () { 147 synchronized (this) { 148 if (sourceRoots == null) { 150 String [] srcProps = getRootProperties(); 151 List <FileObject> result = new ArrayList <FileObject>(); 152 for (String p : srcProps) { 153 String prop = evaluator.getProperty(p); 154 if (prop != null) { 155 FileObject f = helper.getRakeProjectHelper().resolveFileObject(prop); 156 if (f == null) { 157 continue; 158 } 159 if (FileUtil.isArchiveFile(f)) { 160 f = FileUtil.getArchiveRoot(f); 161 } 162 result.add(f); 163 } 164 } 165 sourceRoots = Collections.unmodifiableList(result); 166 } 167 } 168 return sourceRoots.toArray(new FileObject[sourceRoots.size()]); 169 } 170 }); 171 } 172 173 177 public URL [] getRootURLs() { 178 return ProjectManager.mutex().readAccess(new Mutex.Action<URL []>() { 179 public URL [] run () { 180 synchronized (this) { 181 if (sourceRootURLs == null) { 183 String [] srcProps = getRootProperties(); 184 List <URL > result = new ArrayList <URL >(); 185 for (int i = 0; i<srcProps.length; i++) { 186 String prop = evaluator.getProperty(srcProps[i]); 187 if (prop != null) { 188 File f = helper.getRakeProjectHelper().resolveFile(prop); 189 try { 190 URL url = f.toURI().toURL(); 191 if (!f.exists()) { 192 url = new URL (url.toExternalForm() + "/"); } 194 result.add(url); 195 } catch (MalformedURLException e) { 196 ErrorManager.getDefault().notify(e); 197 } 198 } 199 } 200 sourceRootURLs = Collections.unmodifiableList(result); 201 } 202 } 203 return sourceRootURLs.toArray(new URL [sourceRootURLs.size()]); 204 } 205 }); 206 } 207 208 212 public void addPropertyChangeListener (PropertyChangeListener listener) { 213 this.support.addPropertyChangeListener (listener); 214 } 215 216 220 public void removePropertyChangeListener (PropertyChangeListener listener) { 221 this.support.removePropertyChangeListener (listener); 222 } 223 224 225 230 public void putRoots (final URL [] roots, final String [] labels) { 231 ProjectManager.mutex().writeAccess( 232 new Mutex.Action<Void >() { 233 public Void run() { 234 String [] originalProps = getRootProperties(); 235 URL [] originalRoots = getRootURLs(); 236 Map <URL ,String > oldRoots2props = new HashMap <URL ,String >(); 237 for (int i=0; i<originalProps.length;i++) { 238 oldRoots2props.put (originalRoots[i],originalProps[i]); 239 } 240 Map <URL ,String > newRoots2lab = new HashMap <URL ,String >(); 241 for (int i=0; i<roots.length;i++) { 242 newRoots2lab.put (roots[i],labels[i]); 243 } 244 Element cfgEl = helper.getPrimaryConfigurationData(true); 245 NodeList nl = cfgEl.getElementsByTagNameNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, elementName); 246 assert nl.getLength() == 1 : "Illegal project.xml"; Element ownerElement = (Element ) nl.item(0); 248 NodeList rootsNodes = ownerElement.getElementsByTagNameNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, "root"); while (rootsNodes.getLength()>0) { 251 Element root = (Element ) rootsNodes.item(0); 252 ownerElement.removeChild(root); 253 } 254 List <URL > newRoots = Arrays.asList(roots); 256 Map <URL ,String > propsToRemove = new HashMap <URL ,String >(oldRoots2props); 257 propsToRemove.keySet().removeAll(newRoots); 258 EditableProperties props = helper.getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH); 259 props.keySet().removeAll(propsToRemove.values()); 260 helper.putProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH,props); 261 Document doc = ownerElement.getOwnerDocument(); 263 oldRoots2props.keySet().retainAll(newRoots); 264 for (URL newRoot : newRoots) { 265 String rootName = oldRoots2props.get(newRoot); 266 if (rootName == null) { 267 props = helper.getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH); 269 String [] names = newRoot.getPath().split("/"); rootName = MessageFormat.format(newRootNameTemplate, new Object [] {names[names.length - 1], ""}); int rootIndex = 1; 272 while (props.containsKey(rootName)) { 273 rootIndex++; 274 rootName = MessageFormat.format(newRootNameTemplate, new Object [] {names[names.length - 1], rootIndex}); 275 } 276 File f = FileUtil.normalizeFile(new File (URI.create(newRoot.toExternalForm()))); 277 File projDir = FileUtil.toFile(helper.getRakeProjectHelper().getProjectDirectory()); 278 String path = f.getAbsolutePath(); 279 String prjPath = projDir.getAbsolutePath()+File.separatorChar; 280 if (path.startsWith(prjPath)) { 281 path = path.substring(prjPath.length()); 282 } 283 else { 284 path = refHelper.createForeignFileReference(f, RubyProject.SOURCES_TYPE_RUBY); 285 props = helper.getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH); 286 } 287 props.put(rootName,path); 288 helper.putProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH,props); 289 } 290 Element newRootNode = doc.createElementNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, "root"); newRootNode.setAttribute("id",rootName); String label = (String ) newRoots2lab.get (newRoot); 293 if (label != null && label.length()>0 && !label.equals (getRootDisplayName(null,rootName))) { newRootNode.setAttribute("name",label); } 296 ownerElement.appendChild (newRootNode); 297 } 298 helper.putPrimaryConfigurationData(cfgEl,true); 299 return null; 300 } 301 } 302 ); 303 } 304 305 311 public String getRootDisplayName (String rootName, String propName) { 312 if (rootName == null || rootName.length() ==0) { 313 if (isTest && RubyProjectGenerator.DEFAULT_TEST_SRC_NAME.equals(propName)) { rootName = DEFAULT_TEST_LABEL; 316 } 317 else if (!isTest && RubyProjectGenerator.DEFAULT_SRC_NAME.equals(propName)) { rootName = DEFAULT_SOURCE_LABEL; 319 } 320 else { 321 String propValue = evaluator.getProperty(propName); 324 File sourceRoot = propValue == null ? null : helper.getRakeProjectHelper().resolveFile(propValue); 325 rootName = createInitialDisplayName(sourceRoot); 326 } 327 } 328 return rootName; 329 } 330 331 336 public String createInitialDisplayName (File sourceRoot) { 337 String rootName; 338 if (sourceRoot != null) { 339 String srPath = sourceRoot.getAbsolutePath(); 340 String pdPath = projectDir.getAbsolutePath() + File.separatorChar; 341 if (srPath.startsWith(pdPath)) { 342 rootName = srPath.substring(pdPath.length()); 343 } 344 else { 345 rootName = sourceRoot.getAbsolutePath(); 346 } 347 } 348 else { 349 rootName = isTest ? DEFAULT_TEST_LABEL : DEFAULT_SOURCE_LABEL; 350 } 351 return rootName; 352 } 353 354 359 public boolean isTest () { 360 return this.isTest; 361 } 362 363 private void resetCache (boolean isXMLChange, String propName) { 364 boolean fire = false; 365 synchronized (this) { 366 if (isXMLChange) { 368 this.sourceRootProperties = null; 369 this.sourceRootNames = null; 370 this.sourceRoots = null; 371 this.sourceRootURLs = null; 372 fire = true; 373 } else if (propName == null || (sourceRootProperties != null && sourceRootProperties.contains(propName))) { 374 this.sourceRoots = null; 375 this.sourceRootURLs = null; 376 fire = true; 377 } 378 } 379 if (fire) { 380 if (isXMLChange) { 381 this.support.firePropertyChange (PROP_ROOT_PROPERTIES,null,null); 382 } 383 this.support.firePropertyChange (PROP_ROOTS,null,null); 384 } 385 } 386 387 private void readProjectMetadata () { 388 Element cfgEl = helper.getPrimaryConfigurationData(true); 389 NodeList nl = cfgEl.getElementsByTagNameNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, elementName); 390 assert nl.getLength() == 0 || nl.getLength() == 1 : "Illegal project.xml"; List <String > rootProps = new ArrayList <String >(); 392 List <String > rootNames = new ArrayList <String >(); 393 if (nl.getLength()==1) { 395 NodeList roots = ((Element )nl.item(0)).getElementsByTagNameNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, "root"); for (int i=0; i<roots.getLength(); i++) { 397 Element root = (Element ) roots.item(i); 398 String value = root.getAttribute("id"); assert value.length() > 0 : "Illegal project.xml"; 400 rootProps.add(value); 401 value = root.getAttribute("name"); rootNames.add (value); 403 } 404 } 405 this.sourceRootProperties = Collections.unmodifiableList(rootProps); 406 this.sourceRootNames = Collections.unmodifiableList(rootNames); 407 } 408 409 private class ProjectMetadataListener implements PropertyChangeListener ,RakeProjectListener { 410 411 public void propertyChange(PropertyChangeEvent evt) { 412 resetCache (false,evt.getPropertyName()); 413 } 414 415 public void configurationXmlChanged(RakeProjectEvent ev) { 416 resetCache (true,null); 417 } 418 419 public void propertiesChanged(RakeProjectEvent ev) { 420 } 422 } 423 424 } 425 | Popular Tags |