001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.mobility.project.ui.customizer;
043:
044: import java.io.File;
045: import java.nio.charset.Charset;
046: import java.util.ArrayList;
047: import java.util.Arrays;
048: import java.util.Collection;
049: import java.util.Collections;
050: import java.util.HashMap;
051: import java.util.HashSet;
052: import java.util.Iterator;
053: import java.util.List;
054: import java.util.Map;
055: import java.util.Set;
056: import org.netbeans.api.mobility.project.PropertyDescriptor;
057: import org.netbeans.api.mobility.project.ui.customizer.ProjectProperties;
058: import org.netbeans.api.project.Project;
059: import org.netbeans.api.project.ProjectManager;
060: import org.netbeans.api.project.ProjectUtils;
061: import org.netbeans.api.queries.FileEncodingQuery;
062: import org.netbeans.spi.project.ProjectConfiguration;
063: import org.netbeans.modules.mobility.project.DefaultPropertiesDescriptor;
064: import org.netbeans.spi.mobility.deployment.DeploymentPlugin;
065: import org.netbeans.spi.mobility.project.ProjectPropertiesDescriptor;
066: import org.netbeans.spi.mobility.project.PropertyParser;
067: import org.netbeans.spi.mobility.project.support.DefaultPropertyParsers;
068: import org.netbeans.modules.mobility.project.ui.customizer.VisualClassPathItem;
069: import org.netbeans.api.queries.CollocationQuery;
070: import org.netbeans.modules.mobility.project.ProjectConfigurationsHelper;
071: import org.netbeans.spi.project.support.ant.AntProjectHelper;
072: import org.netbeans.spi.project.support.ant.EditableProperties;
073: import org.netbeans.spi.project.support.ant.PropertyUtils;
074: import org.netbeans.spi.project.support.ant.ReferenceHelper;
075: import org.openide.ErrorManager;
076: import org.openide.filesystems.FileObject;
077: import org.openide.filesystems.FileUtil;
078: import org.openide.util.Lookup;
079: import org.openide.util.MutexException;
080: import org.openide.util.Mutex;
081: import org.openide.util.NbBundle;
082:
083: /** Helper class. Defines constants for properties. Knows the proper
084: * place where to store the properties.
085: *
086: * @author Petr Hrebejk, Adam Sotona
087: */
088: public class J2MEProjectProperties implements ProjectProperties {
089:
090: // Special properties of the project
091: public static final String J2ME_PROJECT_NAME = "j2me.project.name"; //NOI18N
092:
093: public static final String PROP_CONFIGURATIONS = "configurations"; //NOI18N
094:
095: public static final String CONFIG_PREFIX = "configs."; //NOI18N
096:
097: private static final String LIBS = "${libs.";
098:
099: // Info about the property destination
100: private final Set<PropertyDescriptor> PROPERTY_DESCRIPTORS = new HashSet<PropertyDescriptor>();
101:
102: private void initPropertyDescriptors() {
103: for (ProjectPropertiesDescriptor p : Lookup
104: .getDefault()
105: .lookup(
106: new Lookup.Template<ProjectPropertiesDescriptor>(
107: ProjectPropertiesDescriptor.class))
108: .allInstances()) {
109: PROPERTY_DESCRIPTORS.addAll(p.getPropertyDescriptors());
110: }
111: for (DeploymentPlugin p : Lookup.getDefault().lookup(
112: new Lookup.Template<DeploymentPlugin>(
113: DeploymentPlugin.class)).allInstances()) {
114: final Iterator it2 = p.getProjectPropertyDefaultValues()
115: .entrySet().iterator();
116: while (it2.hasNext()) {
117: final Map.Entry en = (Map.Entry) it2.next();
118: final Object v = en.getValue();
119: final PropertyParser par = v instanceof Boolean ? DefaultPropertyParsers.BOOLEAN_PARSER
120: : v instanceof Integer ? DefaultPropertyParsers.INTEGER_PARSER
121: : v instanceof String ? DefaultPropertyParsers.STRING_PARSER
122: : v instanceof File ? DefaultPropertyParsers.FILE_REFERENCE_PARSER
123: : null;
124: if (par != null)
125: PROPERTY_DESCRIPTORS.add(new PropertyDescriptor(
126: (String) en.getKey(), true, par, v
127: .toString()));
128: }
129: }
130: }
131:
132: // Private fields ----------------------------------------------------------
133:
134: private Project project;
135:
136: protected ReferenceHelper refHelper;
137: protected AntProjectHelper antProjectHelper;
138: protected HashMap<String, PropertyInfo> properties;
139: protected ProjectConfigurationsHelper configHelper;
140: protected ProjectConfiguration devConfigs[];
141:
142: public J2MEProjectProperties(Project project,
143: AntProjectHelper antProjectHelper,
144: ReferenceHelper refHelper,
145: ProjectConfigurationsHelper configHelper) {
146: this .project = project;
147: this .properties = new HashMap<String, PropertyInfo>();
148: this .antProjectHelper = antProjectHelper;
149: this .refHelper = refHelper;
150: this .configHelper = configHelper;
151: initPropertyDescriptors();
152: read();
153: }
154:
155: public synchronized void setActiveConfiguration(
156: final ProjectConfiguration cfg) {
157: put(DefaultPropertiesDescriptor.CONFIG_ACTIVE,
158: cfg == null
159: || configHelper.getDefaultConfiguration()
160: .equals(cfg) ? "" : cfg
161: .getDisplayName()); //NOI18N
162: }
163:
164: public synchronized ProjectConfiguration getActiveConfiguration() {
165: final String cfg = (String) get(DefaultPropertiesDescriptor.CONFIG_ACTIVE);
166: if (devConfigs == null || cfg == null || cfg.length() == 0)
167: return configHelper.getDefaultConfiguration();
168: for (int i = 0; i < devConfigs.length; i++)
169: if (cfg.equals(devConfigs[i].getDisplayName()))
170: return devConfigs[i];
171: return configHelper.getDefaultConfiguration();
172: }
173:
174: /** XXX to be deleted when introduced in AntPropertyHeleper API
175: */
176: static String getAntPropertyName(final String property) {
177: if (property != null && property.startsWith("${") && // NOI18N
178: property.endsWith("}")) { // NOI18N
179: return property.substring(2, property.length() - 1);
180: }
181: return property;
182: }
183:
184: AntProjectHelper getHelper() {
185: return antProjectHelper;
186: }
187:
188: private PropertyDescriptor findPropertyDescriptor(
189: final String propertyName) {
190: for (PropertyDescriptor pd : PROPERTY_DESCRIPTORS) {
191: if (pd.getName().equals(propertyName)
192: || (propertyName.startsWith(CONFIG_PREFIX) && propertyName
193: .endsWith('.' + pd.getName())))
194: return pd;
195: }
196: return null;
197: }
198:
199: public Object put(final String propertyName, final Object value) {
200: PropertyInfo pi = properties.get(propertyName);
201: if (pi == null) {
202: // new configuration property clone appeard
203: final PropertyDescriptor pd = findPropertyDescriptor(propertyName);
204: assert pd != null : "Unknown property " + propertyName; // NOI18N
205: // not necessary to init when it is goint to be changed just now
206: pi = new PropertyInfo(pd.clone(propertyName), null);
207: properties.put(propertyName, pi);
208: }
209: final Object oldVal = pi.getValue();
210: pi.setValue(value);
211: return oldVal;
212: }
213:
214: void putPropertyRawValue(final Object propertyName,
215: final String rawValue) {
216: PropertyInfo pi = properties.get(propertyName);
217: if (pi == null) {
218: // new configuration property clone appeard
219: final PropertyDescriptor pd = findPropertyDescriptor((String) propertyName);
220: assert pd != null : "Unknown property " + propertyName; // NOI18N
221: // not necessary to init when it is goint to be changed just now
222: pi = new PropertyInfo(pd.clone((String) propertyName), null);
223: properties.put((String) propertyName, pi);
224: }
225: pi.setRawValue(rawValue);
226: }
227:
228: public Object get(final Object propertyName) {
229: final PropertyInfo pi = properties.get(propertyName);
230: return pi == null ? null : pi.getValue();
231: }
232:
233: public String getPropertyRawValue(final Object propertyName) {
234: final PropertyInfo pi = properties.get(propertyName);
235: return pi == null ? null : pi.getRawValue();
236: }
237:
238: public boolean isModified(final String propertyName) {
239: final PropertyInfo pi = properties.get(propertyName);
240: assert pi != null : "Unknown property " + propertyName; // NOI18N
241: return pi.isModified();
242: }
243:
244: public ProjectConfiguration[] getConfigurations() {
245: return devConfigs;
246: }
247:
248: final public synchronized void setConfigurations(
249: final ProjectConfiguration[] configurations) {
250: this .devConfigs = configurations;
251: }
252:
253: public List<String> getAllIdentifiers() {
254: final ArrayList<String> l = new ArrayList<String>();
255: for (int i = 0; i < devConfigs.length; i++) {
256: l.add(devConfigs[i].getDisplayName());
257: final Map<String, Object> abs = (Map<String, Object>) get(configHelper
258: .getDefaultConfiguration().equals(devConfigs[i]) ? DefaultPropertiesDescriptor.ABILITIES
259: : CONFIG_PREFIX + devConfigs[i].getDisplayName()
260: + '.'
261: + DefaultPropertiesDescriptor.ABILITIES);
262: if (abs != null)
263: l.addAll(abs.keySet());
264: }
265: return l;
266: }
267:
268: public FileObject getProjectDirectory() {
269: return project.getProjectDirectory();
270: }
271:
272: public FileObject getSourceRoot() {
273: return antProjectHelper.resolveFileObject(antProjectHelper
274: .getStandardPropertyEvaluator().getProperty("src.dir")); //NOI18N
275: }
276:
277: /** Reads all the properties of the project and converts them to objects
278: * suitable for usage in the GUI controls.
279: */
280: private void read() {
281: // Read the properties from the project
282: EditableProperties sharedProps = antProjectHelper
283: .getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);
284: EditableProperties privateProps = antProjectHelper
285: .getProperties(AntProjectHelper.PRIVATE_PROPERTIES_PATH);
286: final ProjectConfiguration cfgs[] = configHelper
287: .getConfigurations().toArray(
288: new ProjectConfiguration[0]);
289: final ProjectConfiguration confs[] = new ProjectConfiguration[cfgs.length];
290: System.arraycopy(cfgs, 0, confs, 0, cfgs.length);
291: setConfigurations(confs);
292: // Initialize the property map with objects
293: properties.put(J2ME_PROJECT_NAME, new PropertyInfo(
294: new PropertyDescriptor(J2ME_PROJECT_NAME, true,
295: DefaultPropertyParsers.STRING_PARSER),
296: ProjectUtils.getInformation(project).getDisplayName()));
297: for (PropertyDescriptor pd : PROPERTY_DESCRIPTORS) {
298: EditableProperties ep = pd.isShared() ? sharedProps
299: : privateProps;
300: String raw = ep.getProperty(pd.getName());
301: properties.put(pd.getName(), new PropertyInfo(pd,
302: raw == null ? pd.getDefaultValue() : raw));
303: for (int j = 0; j < devConfigs.length; j++) {
304: final PropertyDescriptor clone = pd.clone(CONFIG_PREFIX
305: + devConfigs[j].getDisplayName() + '.'
306: + pd.getName());
307: raw = ep.getProperty(clone.getName());
308: if (raw != null) {
309: properties.put(clone.getName(), new PropertyInfo(
310: clone, raw));
311: }
312: }
313: }
314: }
315:
316: /** Transforms all the Objects from GUI controls into String Ant
317: * properties and stores them in the project
318: */
319: public void store() {
320:
321: try {
322: ProjectManager.mutex().writeAccess(
323: new Mutex.ExceptionAction<Object>() {
324: public Object run() {
325:
326: resolveProjectDependencies();
327:
328: // Some properties need special handling e.g. if the
329: // property changes the project.xml files
330: for (final PropertyInfo pi : properties
331: .values()) {
332: pi.encode();
333: }
334:
335: final ProjectConfiguration configs[] = configHelper
336: .getConfigurations()
337: .toArray(
338: new ProjectConfiguration[0]);
339: final HashSet<ProjectConfiguration> newConfigs = new HashSet<ProjectConfiguration>(
340: Arrays.asList(devConfigs));
341: for (int i = 0; i < configs.length; i++) {
342: if (!newConfigs.remove(configs[i])) {
343: configHelper
344: .removeConfiguration(configs[i]);
345: }
346: }
347: for (ProjectConfiguration cfg : newConfigs) {
348: configHelper.addConfiguration(cfg
349: .getDisplayName());
350: }
351:
352: // Reread the properties. It may have changed when
353: // e.g. when setting references to another projects
354: EditableProperties sharedProps = antProjectHelper
355: .getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);
356: EditableProperties privateProps = antProjectHelper
357: .getProperties(AntProjectHelper.PRIVATE_PROPERTIES_PATH);
358:
359: // Set the changed properties
360: for (final PropertyInfo pi : properties
361: .values()) {
362: final PropertyDescriptor pd = pi
363: .getPropertyDescriptor();
364: if (pd != null && pi.isModified()) {
365: final String newValueEncoded = pi
366: .getNewValueEncoded();
367: if (newValueEncoded != null) {
368: (pd.isShared() ? sharedProps
369: : privateProps)
370: .setProperty(pd
371: .getName(),
372: newValueEncoded);
373: } else {
374: // remove property
375: (pd.isShared() ? sharedProps
376: : privateProps)
377: .remove(pd.getName());
378: }
379: }
380: }
381:
382: // Store the property changes into the project
383: antProjectHelper
384: .putProperties(
385: AntProjectHelper.PROJECT_PROPERTIES_PATH,
386: sharedProps);
387: antProjectHelper
388: .putProperties(
389: AntProjectHelper.PRIVATE_PROPERTIES_PATH,
390: privateProps);
391:
392: // Invoke this method to perform cyclic dependencies check and potentionally show warning dilalog
393: CustomizerGeneral cg = new CustomizerGeneral();
394: cg.initValues(J2MEProjectProperties.this ,
395: null);
396: cg.getSortedSubprojectsList();
397:
398: //storing global default encoding by dcurrent project (see issue #97855)
399: String enc = sharedProps
400: .getProperty(DefaultPropertiesDescriptor.JAVAC_ENCODING);
401: if (enc != null)
402: FileEncodingQuery
403: .setDefaultEncoding(Charset
404: .forName(enc));
405:
406: return null;
407: }
408: });
409: } catch (MutexException e) {
410: ErrorManager.getDefault().notify(e.getException());
411: }
412:
413: }
414:
415: /** Finds out what are new and removed project dependencies and
416: * applyes the info to the project
417: */
418: protected void resolveProjectDependencies() {
419: // Create a set of old and new artifacts.
420: final Set<String> oldReferences = new HashSet<String>();
421: final Set<String> newReferences = new HashSet<String>();
422: for (PropertyInfo pi : properties.values()) {
423: if (pi != null) {
424: if (pi.getPropertyDescriptor().getPropertyParser() == DefaultPropertyParsers.PATH_PARSER) {
425: // Get original artifacts
426: final List oldList = (List) pi.getOldValue();
427: if (oldList != null) {
428: final Iterator it = oldList.iterator();
429: while (it.hasNext())
430: oldReferences.add(((VisualClassPathItem) it
431: .next()).getRawText());
432: }
433:
434: // Get artifacts after the edit
435: final List newList = (List) pi.getValue();
436: if (newList != null) {
437: final Iterator it = newList.iterator();
438: while (it.hasNext())
439: newReferences.add(((VisualClassPathItem) it
440: .next()).getRawText());
441: }
442: } else if (pi.getPropertyDescriptor()
443: .getPropertyParser() == DefaultPropertyParsers.FILE_REFERENCE_PARSER) {
444: oldReferences.add(pi.getOldRawValue());
445: newReferences.add(pi.getRawValue());
446: }
447: }
448: }
449:
450: // Create set of removed artifacts and remove them
451: final Set<String> removed = new HashSet<String>(oldReferences);
452: removed.removeAll(newReferences);
453: final Set<String> added = new HashSet<String>(newReferences);
454: added.removeAll(oldReferences);
455:
456: // 1. first remove all project references. The method will modify
457: // project property files, so it must be done separately
458: for (String reference : removed) {
459: if (reference != null && !reference.startsWith(LIBS)) { //NOI18N
460: refHelper.destroyReference(reference);
461: }
462: }
463:
464: // 2. now read project.properties and modify rest
465: final EditableProperties ep = antProjectHelper
466: .getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);
467: boolean changed = false;
468:
469: for (final String reference : removed) {
470: if (reference != null && reference.startsWith(LIBS)) { //NOI18N
471: // remove helper property pointing to library jar if there is any
472: ep.remove(reference
473: .substring(2, reference.length() - 1));
474: changed = true;
475: }
476: }
477: final File projDir = FileUtil.toFile(antProjectHelper
478: .getProjectDirectory());
479: for (String reference : added) {
480: if (reference != null && reference.startsWith(LIBS)) { //NOI18N
481: // add property to project.properties pointing to relativized
482: // library jar(s) if possible
483: reference = reference.substring(2,
484: reference.length() - 1);
485: final String value = relativizeLibraryClasspath(
486: reference, projDir);
487: if (value != null) {
488: ep.setProperty(reference, value);
489: ep.setComment(reference, new String[] {
490: NbBundle.getMessage(
491: J2MEProjectProperties.class,
492: "DESC_J2MEProps_CommentLine1",
493: reference), //NOI18N
494: NbBundle.getMessage(
495: J2MEProjectProperties.class,
496: "DESC_J2MEProps_CommentLine2") },
497: false); //NOI18N
498: changed = true;
499: }
500: }
501: }
502: if (changed) {
503: antProjectHelper.putProperties(
504: AntProjectHelper.PROJECT_PROPERTIES_PATH, ep);
505: }
506:
507: }
508:
509: /**
510: * Tokenize library classpath and try to relativize all the jars.
511: * @param property library property name ala "libs.someLib.classpath"
512: * @param projectDir project dir for relativization
513: * @return relativized library classpath or null if some jar is not collocated
514: */
515: private String relativizeLibraryClasspath(final String property,
516: final File projectDir) {
517: final String value = PropertyUtils.getGlobalProperties()
518: .getProperty(property);
519: if (value == null)
520: return null;
521: final String[] paths = PropertyUtils.tokenizePath(value);
522: final StringBuffer sb = new StringBuffer();
523: for (int i = 0; i < paths.length; i++) {
524: final File f = antProjectHelper.resolveFile(paths[i]);
525: if (CollocationQuery.areCollocated(f, projectDir)) {
526: sb.append(PropertyUtils.relativizeFile(projectDir, f));
527: } else {
528: return null;
529: }
530: if (i + 1 < paths.length) {
531: sb.append(File.pathSeparatorChar);
532: }
533: }
534: if (sb.length() == 0) {
535: return null;
536: }
537: return sb.toString();
538: }
539:
540: public void clear() {
541: throw new UnsupportedOperationException();
542: }
543:
544: public boolean containsKey(final Object key) {
545: return properties.containsKey(key);
546: }
547:
548: public boolean containsValue(final Object value) {
549: return properties.containsValue(value);
550: }
551:
552: public Set entrySet() {
553: return Collections.unmodifiableSet(properties.entrySet());
554: }
555:
556: public boolean isEmpty() {
557: return properties.isEmpty();
558: }
559:
560: public Set<String> keySet() {
561: return Collections.unmodifiableSet(properties.keySet());
562: }
563:
564: public void putAll(@SuppressWarnings("unused")
565: final Map t) {
566: throw new UnsupportedOperationException();
567: }
568:
569: public Object remove(final Object key) {
570: return containsKey(key) ? put((String) key, null) : null;
571: }
572:
573: public int size() {
574: return properties.size();
575: }
576:
577: public Collection values() {
578: return Collections.unmodifiableCollection(properties.values());
579: }
580:
581: private class PropertyInfo {
582:
583: final protected PropertyDescriptor propertyDesciptor;
584: final private String rawValue;
585: final private Object value;
586: private Object newValue;
587: private String newValueEncoded;
588: private boolean modified;
589:
590: public PropertyInfo(PropertyDescriptor propertyDesciptor,
591: String rawValue) {
592: this .propertyDesciptor = propertyDesciptor;
593: this .rawValue = rawValue;
594: this .value = rawValue == null ? null : propertyDesciptor
595: .getPropertyParser().decode(rawValue,
596: antProjectHelper, refHelper);
597: this .newValue = null;
598: }
599:
600: public PropertyDescriptor getPropertyDescriptor() {
601: return propertyDesciptor;
602: }
603:
604: public void encode() {
605: if (isModified() && newValue != null) {
606: newValueEncoded = propertyDesciptor.getPropertyParser()
607: .encode(newValue, antProjectHelper, refHelper);
608: } else {
609: newValueEncoded = null;
610: }
611: }
612:
613: public Object getValue() {
614: return isModified() ? newValue : value;
615: }
616:
617: public String getRawValue() {
618: encode();
619: return isModified() ? newValueEncoded : rawValue;
620: }
621:
622: public void setValue(final Object value) {
623: newValue = value;
624: modified = true;
625: }
626:
627: public void setRawValue(final String rawValue) {
628: setValue(rawValue == null ? null : propertyDesciptor
629: .getPropertyParser().decode(rawValue,
630: antProjectHelper, refHelper));
631: }
632:
633: public String getNewValueEncoded() {
634: return newValueEncoded;
635: }
636:
637: public boolean isModified() {
638: return modified;
639: }
640:
641: public Object getOldValue() {
642: return value;
643: }
644:
645: public String getOldRawValue() {
646: return rawValue;
647: }
648: }
649: }
|