/*******************************************************************************
* Copyright (c) 2012 Pivotal Software, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package org.grails.ide.eclipse.explorer.preferences;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.CLASSPATH_CONTAINERS;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.CONF;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.CONTROLLERS;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.DOMAIN;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.I18N;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.PLUGINS;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.SCRIPTS;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.SERVICES;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.TAGLIB;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.TEST_REPORTS;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.UTILS;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.VIEWS;
import static org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes.ASSETS;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.grails.ide.eclipse.core.GrailsCoreActivator;
import org.grails.ide.eclipse.core.internal.plugins.GrailsProjectStructureTypes;
import org.grails.ide.eclipse.core.util.ArrayEncoder;
import org.grails.ide.eclipse.explorer.elements.ILogicalFolder;
import org.grails.ide.eclipse.explorer.providers.GrailsCommonNavigatorViewerSorter;
/**
* An instance of this class represents info that configures the {@link GrailsCommonNavigatorViewerSorter}.
* <p>
* Essentially it is a list of categories.
*
* @author Kris De Volder
*
* @since 2.8
*/
public class OrderingConfig {
private static void debug(String string) {
// System.out.println(string);
}
/**
* An array containing both Strings and GrailsProjectStructureTypes. String are used to determine sorting
* order of specific packageFragment root (like src/java, src/groovy) or IFile or IFolder elements for
* which there is no corresponding element in {@link GrailsProjectStructureTypes}.
*/
private Object[] categories;
/**
* Maps a 'sort key' to a category used for sorting. Lower number means it show higher-up in the view.
*/
private Map<String, Integer> sortKeyMap = null;
private int defaultSortCat; //larger than any of the "at the top" items cat.
private OrderingConfig(Object... atTheTop) {
this.categories = atTheTop;
}
private void lazyInit() {
if (sortKeyMap==null) {
defaultSortCat = categories.length;
sortKeyMap = new HashMap<String, Integer>(categories.length);
for (int i = 0; i < categories.length; i++) {
sortKeyMap.put(configElementToKey(categories[i]), i);
}
}
}
/**
* This array defines the default sort order for all the "special" elements in the viewer. The special elements
* should always come before "the rest of them". The rest of them are sorted by a basic Java element
* sorter.
*/
public static final OrderingConfig DEFAULT = new OrderingConfig(
DOMAIN,
CONTROLLERS,
VIEWS,
ASSETS,
TAGLIB,
SERVICES,
UTILS,
SCRIPTS,
I18N,
CONF,
"src/java",
"src/groovy",
"test/unit",
"test/integration",
TEST_REPORTS,
PLUGINS,
CLASSPATH_CONTAINERS,
"application.properties"
//Then the rest of them in no particular order
);
public int size() {
return categories.length;
}
public Object get(int i) {
return categories[i];
}
/**
* Passed to the list viewer in the UI.
*/
public List<Object> asList() {
return Arrays.asList(categories);
}
/**
* Convert back from a list from the viewer in the UI.
*/
public static OrderingConfig fromList(List<Object> elements) {
return new OrderingConfig(elements.toArray());
}
/**
* Converts an element found in the viewer to a key that can be looked up in sortKeyMap
*/
private static String viewerElementToKey(Object viewerElement) {
IResource rsrc = toResource(viewerElement);
if (rsrc!=null) {
IPath path = rsrc.getProjectRelativePath();
return path.toString();
} else if (viewerElement instanceof ILogicalFolder) {
return ((ILogicalFolder) viewerElement).getType().getFolderName();
}
return null;
}
private static IResource toResource(Object object) {
if (object instanceof IResource) {
return (IResource) object;
} else if (object instanceof IPackageFragmentRoot) {
try {
return ((IPackageFragmentRoot) object).getCorrespondingResource();
} catch (JavaModelException e) {
GrailsCoreActivator.log(e);
}
}
return null;
}
/**
* Convert a config element object into a String key (that can
* be derived also from viewer elements) and looked up in sortKeyMap.
*/
private static String configElementToKey(Object o) {
if (o instanceof String) {
return (String)o;
} else if (o instanceof GrailsProjectStructureTypes) {
return ((GrailsProjectStructureTypes) o).getFolderName();
}
throw new IllegalArgumentException("The sort config aray must only contain Strings (project relative resource path)" +
" or GrailsProjectStructureTypes");
}
/**
* Given a viewer element determine its 'sort category' as specified by this configuration.
*/
public int getSortCat(Object viewerElement) {
lazyInit();
// debug(">>> getSortCat "+object);
String key = viewerElementToKey(viewerElement);
if (key!=null) {
Integer cat = sortKeyMap.get(key);
if (cat != null) {
debug("<<< getSortCat "+cat);
return cat;
}
}
// debug("<<< getSortCat "+DEFAULT_SORT_CAT);
return defaultSortCat; //is larger than any other one in the map!
}
/**
* Encodes this config into a single String that can be stored into an eclipse preferences store, File etc.
*/
public String toSaveString() {
String[] encodedStrs = new String[categories.length];
for (int i = 0; i < encodedStrs.length; i++) {
encodedStrs[i] = toSaveString(categories[i]);
}
return ArrayEncoder.encode(encodedStrs);
}
private String toSaveString(Object configElement) {
if (configElement instanceof String) {
return "S"+configElement;
} else if (configElement instanceof GrailsProjectStructureTypes) {
return "E"+configElement;
} else {
throw new IllegalArgumentException("OrderingConfig elements should only be String or GrailsProjectStructureTypes");
}
}
public static OrderingConfig fromSaveString(String encoded) {
try {
String[] encodedStrs = ArrayEncoder.decode(encoded);
Object[] configElements = new Object[encodedStrs.length];
for (int i = 0; i < configElements.length; i++) {
configElements[i] = elementFromSaveString(encodedStrs[i]);
}
return new OrderingConfig(configElements);
} catch (Exception e) {
GrailsCoreActivator.log(e);
return DEFAULT;
}
}
private static Object elementFromSaveString(String encoded) throws DecodeException {
if (encoded.length()>0) {
char kind = encoded.charAt(0);
switch (kind) {
case 'E':
return GrailsProjectStructureTypes.valueOf(encoded.substring(1));
case 'S':
return encoded.substring(1);
default:
}
}
throw new DecodeException("Can't decode ordering config element from: "+encoded);
}
private static class DecodeException extends Exception {
private static final long serialVersionUID = 1L;
public DecodeException(String string) {
super(string);
}
}
}