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.apisupport.project.ui.customizer;
043:
044: import java.awt.Component;
045: import java.io.CharConversionException;
046: import java.util.Arrays;
047: import java.util.Collection;
048: import java.util.Collections;
049: import java.util.HashMap;
050: import java.util.HashSet;
051: import java.util.Iterator;
052: import java.util.Map;
053: import java.util.Set;
054: import java.util.SortedSet;
055: import java.util.TreeSet;
056: import javax.swing.AbstractListModel;
057: import javax.swing.ComboBoxModel;
058: import javax.swing.DefaultComboBoxModel;
059: import javax.swing.DefaultListCellRenderer;
060: import javax.swing.DefaultListModel;
061: import javax.swing.JList;
062: import javax.swing.ListCellRenderer;
063: import javax.swing.ListModel;
064: import javax.swing.table.AbstractTableModel;
065: import org.netbeans.api.project.Project;
066: import org.netbeans.api.project.ProjectUtils;
067: import org.netbeans.modules.apisupport.project.NbModuleProject;
068: import org.netbeans.modules.apisupport.project.Util;
069: import org.netbeans.modules.apisupport.project.universe.ModuleEntry;
070: import org.openide.awt.HtmlRenderer;
071: import org.openide.util.NbBundle;
072: import org.openide.xml.XMLUtil;
073:
074: /**
075: * @author mkrauskopf
076: */
077: public final class CustomizerComponentFactory {
078:
079: /** Generally usable in conjuction with {@link #createComboWaitModel}. */
080: public static final String WAIT_VALUE = NbBundle.getMessage(
081: CustomizerComponentFactory.class,
082: "ComponentFactory_please_wait");
083:
084: /** Generally usable in conjuction with {@link #createComboEmptyModel}. */
085: public static final String EMPTY_VALUE = NbBundle.getMessage(
086: CustomizerComponentFactory.class, "LBL_Empty");
087:
088: static DependencyListModel INVALID_DEP_LIST_MODEL;
089:
090: private static final String INVALID_PLATFORM = "<html><font color=\"!nb.errorForeground\"><" // NOI18N
091: + NbBundle.getMessage(CustomizerComponentFactory.class,
092: "MSG_InvalidPlatform") + "></font></html>"; // NOI18N
093:
094: private CustomizerComponentFactory() {
095: // don't allow instances
096: }
097:
098: /**
099: * Use this model in situation when you need to populate combo in the
100: * background. The only item in this model is {@link #WAIT_VALUE}.
101: */
102: public static ComboBoxModel createComboWaitModel() {
103: return new DefaultComboBoxModel(new Object[] { WAIT_VALUE });
104: }
105:
106: /** The only item in this model is {@link #EMPTY_VALUE}. */
107: public static ComboBoxModel createComboEmptyModel() {
108: return new DefaultComboBoxModel(new Object[] { EMPTY_VALUE });
109: }
110:
111: /**
112: * Conveninent method which delegates to {@link #hasOnlyValue} passing a
113: * given model and {@link #WAIT_VALUE} as a value.
114: */
115: public static boolean isWaitModel(final ListModel model) {
116: return hasOnlyValue(model,
117: CustomizerComponentFactory.WAIT_VALUE);
118: }
119:
120: /**
121: * Returns true if the given model is not <code>null</code> and contains
122: * only the given value.
123: */
124: public static boolean hasOnlyValue(final ListModel model,
125: final Object value) {
126: return model != null && model.getSize() == 1
127: && model.getElementAt(0) == value;
128: }
129:
130: /**
131: * Use this model in situation when you need to populate list in the
132: * background. The only item in this model is {@link #WAIT_VALUE}.
133: *
134: * @see #isWaitModel
135: */
136: public static ListModel createListWaitModel() {
137: DefaultListModel listWaitModel = new DefaultListModel();
138: listWaitModel.addElement(WAIT_VALUE);
139: return listWaitModel;
140: }
141:
142: /**
143: * Creates a list model for a set of module dependencies.
144: * The dependencies will be sorted by module display name.
145: */
146: static CustomizerComponentFactory.DependencyListModel createSortedDependencyListModel(
147: final Set<ModuleDependency> deps) {
148: assert deps != null;
149: return new CustomizerComponentFactory.DependencyListModel(deps,
150: true);
151: }
152:
153: /**
154: * Creates a list model for a set of module dependencies.
155: * The dependencies will be left in the order given.
156: */
157: static CustomizerComponentFactory.DependencyListModel createDependencyListModel(
158: final Set<ModuleDependency> deps) {
159: assert deps != null;
160: return new CustomizerComponentFactory.DependencyListModel(deps,
161: false);
162: }
163:
164: static synchronized CustomizerComponentFactory.DependencyListModel getInvalidDependencyListModel() {
165: if (INVALID_DEP_LIST_MODEL == null) {
166: INVALID_DEP_LIST_MODEL = new DependencyListModel();
167: }
168: return INVALID_DEP_LIST_MODEL;
169: }
170:
171: static ListCellRenderer/*<ModuleDependency|WAIT_VALUE>*/getDependencyCellRenderer(
172: boolean boldfaceApiModules) {
173: return new DependencyListCellRenderer(boldfaceApiModules);
174: }
175:
176: static ListCellRenderer/*<Project>*/getModuleCellRenderer() {
177: return new ProjectListCellRenderer();
178: }
179:
180: static ListCellRenderer/*<ModuleEntry>*/getModuleEntryCellRenderer() {
181: return new ModuleEntryListCellRenderer();
182: }
183:
184: static final class DependencyListModel extends AbstractListModel {
185:
186: private final Set<ModuleDependency> currentDeps;
187: private final Set<ModuleDependency> addedDeps = new HashSet<ModuleDependency>();
188: private final Set<ModuleDependency> removedDeps = new HashSet<ModuleDependency>();
189: private final Map<ModuleDependency, ModuleDependency> editedDeps = new HashMap<ModuleDependency, ModuleDependency>();
190:
191: private boolean changed;
192: private final boolean invalid;
193:
194: DependencyListModel(Set<ModuleDependency> deps, boolean sorted) {
195: if (sorted) {
196: currentDeps = new TreeSet<ModuleDependency>(
197: ModuleDependency.LOCALIZED_NAME_COMPARATOR);
198: currentDeps.addAll(deps);
199: } else {
200: currentDeps = deps;
201: }
202: invalid = false;
203: }
204:
205: DependencyListModel() {
206: currentDeps = Collections.emptySet();
207: invalid = true;
208: }
209:
210: public int getSize() {
211: return invalid ? 1 : currentDeps.size();
212: }
213:
214: public Object getElementAt(int i) {
215: return invalid ? INVALID_PLATFORM
216: : currentDeps.toArray()[i];
217: }
218:
219: ModuleDependency getDependency(int i) {
220: return (ModuleDependency) getElementAt(i);
221: }
222:
223: void addDependency(ModuleDependency dep) {
224: if (!currentDeps.contains(dep)) {
225: int origSize = currentDeps.size();
226: currentDeps.add(dep);
227: removedDeps.remove(dep); // be sure it won't get removed
228: changed = true;
229: this .fireContentsChanged(this , 0, origSize);
230: }
231: }
232:
233: void removeDependencies(Collection<ModuleDependency> deps) {
234: int origSize = currentDeps.size();
235: currentDeps.removeAll(deps);
236: for (ModuleDependency entry : deps) {
237: if (!addedDeps.remove(entry)) {
238: removedDeps.add(entry);
239: }
240: }
241: changed = true;
242: this .fireContentsChanged(this , 0, origSize);
243: }
244:
245: void editDependency(ModuleDependency origDep,
246: ModuleDependency newDep) {
247: editedDeps.put(origDep, newDep);
248: currentDeps.remove(origDep);
249: currentDeps.add(newDep);
250: changed = true;
251: this .fireContentsChanged(this , 0, currentDeps.size());
252: }
253:
254: Set<ModuleDependency> getDependencies() {
255: return Collections.unmodifiableSet(currentDeps);
256: }
257:
258: Set<ModuleDependency> getRemovedDependencies() {
259: return removedDeps;
260: }
261:
262: Set<ModuleDependency> getAddedDependencies() {
263: return addedDeps;
264: }
265:
266: Map<ModuleDependency, ModuleDependency> getEditedDependencies() {
267: return editedDeps;
268: }
269:
270: /**
271: * Tries to find if a given dependency has already been edited. If yes,
272: * returns the edited counterpart; <code>null</code> otherwise.
273: */
274: ModuleDependency findEdited(ModuleDependency toFind) {
275: for (Iterator it = editedDeps.values().iterator(); it
276: .hasNext();) {
277: ModuleDependency curr = (ModuleDependency) it.next();
278: if (curr.getModuleEntry().getCodeNameBase().equals(
279: toFind.getModuleEntry().getCodeNameBase())) {
280: return curr;
281: }
282: }
283: return null;
284: }
285:
286: boolean isChanged() {
287: return changed;
288: }
289: }
290:
291: private static final class DependencyListCellRenderer implements
292: ListCellRenderer {
293:
294: private final HtmlRenderer.Renderer renderer = HtmlRenderer
295: .createRenderer();
296: private final boolean boldfaceApiModules;
297:
298: public DependencyListCellRenderer(boolean boldfaceApiModules) {
299: this .boldfaceApiModules = boldfaceApiModules;
300: }
301:
302: public Component getListCellRendererComponent(JList list,
303: Object value, int index, boolean isSelected,
304: boolean cellHasFocus) {
305: String text;
306: if (value == WAIT_VALUE) {
307: text = WAIT_VALUE;
308: } else if (value == INVALID_PLATFORM) {
309: text = INVALID_PLATFORM;
310: renderer.setHtml(true);
311: } else {
312: ModuleDependency md = (ModuleDependency) value;
313: // XXX the following is wrong; spec requires additional logic:
314: boolean bold = boldfaceApiModules
315: && md.getModuleEntry().getPublicPackages().length > 0;
316: boolean deprecated = md.getModuleEntry().isDeprecated();
317: renderer.setHtml(bold || deprecated);
318: String locName = md.getModuleEntry().getLocalizedName();
319: text = locName;
320: if (bold || deprecated) {
321: try {
322: text = "<html>" + (bold ? "<b>" : "")
323: + (deprecated ? "<s>" : "")
324: + XMLUtil.toElementContent(locName); // NOI18N
325: } catch (CharConversionException e) {
326: // forget it
327: }
328: }
329: }
330: return renderer.getListCellRendererComponent(list, text,
331: index, isSelected, cellHasFocus);
332: }
333:
334: }
335:
336: private static class ProjectListCellRenderer extends
337: DefaultListCellRenderer {
338:
339: public Component getListCellRendererComponent(JList list,
340: Object value, int index, boolean isSelected,
341: boolean cellHasFocus) {
342: Component c = super .getListCellRendererComponent(list,
343: ProjectUtils.getInformation((Project) value)
344: .getDisplayName(), index, isSelected,
345: cellHasFocus);
346: return c;
347: }
348:
349: }
350:
351: private static class ModuleEntryListCellRenderer extends
352: DefaultListCellRenderer {
353:
354: public Component getListCellRendererComponent(JList list,
355: Object value, int index, boolean isSelected,
356: boolean cellHasFocus) {
357: ModuleEntry me = (ModuleEntry) value;
358: Component c = super .getListCellRendererComponent(list, me
359: .getLocalizedName(), index, isSelected,
360: cellHasFocus);
361: return c;
362: }
363:
364: }
365:
366: static final class PublicPackagesTableModel extends
367: AbstractTableModel {
368:
369: private Boolean[] selected;
370: private Boolean[] originalSelected;
371: private String[] pkgNames;
372:
373: PublicPackagesTableModel(Map<String, Boolean> publicPackages) {
374: reloadData(publicPackages);
375: }
376:
377: void reloadData(Map<String, Boolean> publicPackages) {
378: selected = new Boolean[publicPackages.size()];
379: publicPackages.values().toArray(selected);
380: originalSelected = new Boolean[publicPackages.size()];
381: System.arraycopy(selected, 0, originalSelected, 0,
382: selected.length);
383: pkgNames = new String[publicPackages.size()];
384: publicPackages.keySet().toArray(pkgNames);
385: fireTableDataChanged();
386: }
387:
388: public int getRowCount() {
389: return pkgNames.length;
390: }
391:
392: public int getColumnCount() {
393: return 2;
394: }
395:
396: public Object getValueAt(int rowIndex, int columnIndex) {
397: if (columnIndex == 0) {
398: return selected[rowIndex];
399: } else {
400: return pkgNames[rowIndex];
401: }
402: }
403:
404: public Class getColumnClass(int columnIndex) {
405: if (columnIndex == 0) {
406: return Boolean.class;
407: } else {
408: return String.class;
409: }
410: }
411:
412: public void setValueAt(Object aValue, int rowIndex,
413: int columnIndex) {
414: assert columnIndex == 0 : "Who is trying to modify second column?"; // NOI18N
415: selected[rowIndex] = (Boolean) aValue;
416: fireTableCellUpdated(rowIndex, 0);
417: }
418:
419: String[] getSelectedPackages() {
420: Set<String> s = new TreeSet<String>();
421: for (int i = 0; i < pkgNames.length; i++) {
422: if (selected[i]) {
423: s.add(pkgNames[i]);
424: }
425: }
426: String[] result = new String[s.size()];
427: s.toArray(result);
428: return result;
429: }
430:
431: public boolean isChanged() {
432: return !Arrays.asList(selected).equals(
433: Arrays.asList(originalSelected));
434: }
435:
436: }
437:
438: static final class FriendListModel extends AbstractListModel {
439:
440: private final Set<String> friends = new TreeSet<String>();
441:
442: private boolean changed;
443:
444: FriendListModel(String[] friends) {
445: if (friends != null) {
446: this .friends.addAll(Arrays.asList(friends));
447: }
448: }
449:
450: public Object getElementAt(int index) {
451: if (index >= friends.size()) {
452: return null;
453: } else {
454: return friends.toArray()[index];
455: }
456: }
457:
458: public int getSize() {
459: return friends.size();
460: }
461:
462: void addFriend(String friend) {
463: friends.add(friend);
464: changed = true;
465: super .fireIntervalAdded(this , 0, friends.size());
466: }
467:
468: void removeFriend(String friend) {
469: friends.remove(friend);
470: changed = true;
471: super .fireIntervalRemoved(this , 0, friends.size());
472: }
473:
474: String[] getFriends() {
475: String[] result = new String[friends.size()];
476: return friends.toArray(result);
477: }
478:
479: boolean isChanged() {
480: return changed;
481: }
482: }
483:
484: static final class RequiredTokenListModel extends AbstractListModel {
485:
486: private final SortedSet<String> tokens;
487: private boolean changed;
488:
489: RequiredTokenListModel(final SortedSet<String> tokens) {
490: this .tokens = new TreeSet<String>(tokens);
491: }
492:
493: public Object getElementAt(int index) {
494: return index >= tokens.size() ? null
495: : tokens.toArray()[index];
496: }
497:
498: public int getSize() {
499: return tokens.size();
500: }
501:
502: void addToken(String token) {
503: tokens.add(token);
504: changed = true;
505: super .fireIntervalAdded(this , 0, tokens.size());
506: }
507:
508: void removeToken(String token) {
509: tokens.remove(token);
510: changed = true;
511: super .fireIntervalRemoved(this , 0, tokens.size());
512: }
513:
514: String[] getTokens() {
515: String[] result = new String[tokens.size()];
516: return tokens.toArray(result);
517: }
518:
519: boolean isChanged() {
520: return changed;
521: }
522:
523: }
524:
525: static final class SuiteSubModulesListModel extends
526: AbstractListModel {
527:
528: private final Set<NbModuleProject> subModules;
529:
530: private boolean changed;
531:
532: SuiteSubModulesListModel(Set<NbModuleProject> subModules) {
533: this .subModules = new TreeSet<NbModuleProject>(Util
534: .projectDisplayNameComparator());
535: this .subModules.addAll(subModules);
536: }
537:
538: public int getSize() {
539: return subModules.size();
540: }
541:
542: public Object getElementAt(int i) {
543: return subModules.toArray()[i];
544: }
545:
546: boolean contains(Project module) {
547: return subModules.contains(module);
548: }
549:
550: void removeModules(Collection modules) {
551: int origSize = subModules.size();
552: subModules.removeAll(modules);
553: changed = true;
554: this .fireContentsChanged(this , 0, origSize);
555: }
556:
557: boolean addModule(NbModuleProject module) {
558: int origSize = subModules.size();
559: boolean added = subModules.add(module);
560: changed = true;
561: this .fireContentsChanged(this , 0, origSize + 1);
562: return added;
563: }
564:
565: public Set<NbModuleProject> getSubModules() {
566: return subModules;
567: }
568:
569: public boolean isChanged() {
570: return changed;
571: }
572: }
573:
574: }
|