/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.properties;
import gw.fs.IDirectory;
import gw.fs.IFile;
import gw.lang.reflect.IType;
import gw.lang.reflect.RefreshKind;
import gw.lang.reflect.TypeLoaderBase;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.module.IModule;
import gw.util.concurrent.LockingLazyVar;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class PropertiesTypeLoader extends TypeLoaderBase {
public static final Set<String> EXTENSIONS = Collections.singleton("properties");
private static final String FILE_EXTENSION = "properties";
private static final String DISPLAY_PROPERTIES = "display.properties";
protected Set<String> _namespaces;
private List<PropertySetSource> _sources;
private final LockingLazyVar<Map<PropertySetSource,TypeNameSet>> _rootTypeNames = new LockingLazyVar<Map<PropertySetSource,TypeNameSet>>() {
@Override
protected Map<PropertySetSource,TypeNameSet> init() {
Map<PropertySetSource,TypeNameSet> result = new HashMap<PropertySetSource, TypeNameSet>();
for (PropertySetSource source : _sources) {
result.put(source, new TypeNameSet(source.getPropertySetNames()));
}
return result;
}
};
public PropertiesTypeLoader(IModule module) {
super(module);
initSources(module);
}
private void initSources(IModule module) {
_sources = Arrays.asList(
SystemPropertiesPropertySet.SOURCE,
new PropertiesPropertySet.Source(module)
);
}
@Override
public Set<String> computeTypeNames() {
Set<String> result = new HashSet<String>();
for (TypeNameSet names : _rootTypeNames.get().values()) {
names.addTo(result);
}
return result;
}
@Override
public List<String> getHandledPrefixes() {
return Collections.emptyList();
}
@Override
public boolean handlesNonPrefixLoads() {
return true;
}
@Override
public boolean handlesFile( IFile file ) {
return FILE_EXTENSION.equalsIgnoreCase( file.getExtension() ) &&
!isDisplayPropertiesFile( file.getName() );
}
public static boolean isDisplayPropertiesFile( String fileName ) {
return fileName.toLowerCase().endsWith( DISPLAY_PROPERTIES );
}
@Override
public IType getType(String fullyQualifiedName) {
// hack to not allow property file in jline to shadow the java type
if (fullyQualifiedName.startsWith("jline.")) {
return null;
}
for (PropertySetSource source : _sources) {
TypeNameSet typeNameSet = _rootTypeNames.get().get(source);
if (typeNameSet != null) {
List<String> possibleMatches = typeNameSet.findMatchesFor(fullyQualifiedName);
for (String possibleMatch : possibleMatches) {
Map<String, IType> propertySetTypes = createPropertyTypesForPropertySetWithName(source, possibleMatch);
if (propertySetTypes.containsKey(fullyQualifiedName)) {
return propertySetTypes.get(fullyQualifiedName);
}
}
}
}
return null;
}
@Override
public void refreshedImpl() {
_rootTypeNames.clear();
}
private Map<String,IType> createPropertyTypesForPropertySetWithName(PropertySetSource source, String name) {
HashMap<String, IType> resultMap = new HashMap<String, IType>();
createPropertyTypesFromPropertyNodeTree(resultMap, PropertyNode.buildTree(source.getPropertySet(name)), source.getFile(name));
return resultMap;
}
private IType createPropertyTypesFromPropertyNodeTree( HashMap<String, IType> resultMap, PropertyNode node, IFile file) {
IType result = TypeSystem.getOrCreateTypeReference(new PropertiesType(this, node, file));
resultMap.put(result.getName(), result);
for (PropertyNode child : node.getChildren()) {
if (!child.isLeaf()) {
createPropertyTypesFromPropertyNodeTree(resultMap, child, file);
}
}
return result;
}
@Override
public String[] getTypesForFile(IFile file) {
List<String> types = new ArrayList<String>();
boolean foundSource = false;
for (PropertySetSource source : _sources) {
PropertySet ps = source.getPropertySetForFile(file);
if (ps != null) {
foundSource = true;
HashMap<String, IType> resultMap = new HashMap<String, IType>();
createPropertyTypesFromPropertyNodeTree(resultMap, PropertyNode.buildTree(ps), file);
for (IType type : resultMap.values()) {
types.add(type.getName());
}
}
}
if(!foundSource) {
initSources(_module);
_rootTypeNames.clear();
}
return types.toArray(new String[types.size()]);
}
@Override
public RefreshKind refreshedFile(IFile file, String[] types, RefreshKind kind) {
_rootTypeNames.clear();
return kind;
}
/**
* Set of case insensitive type names with operation to quickly find which type names in the set are possible
* matches for a full type name. A match is a prefix which is either an exact match or matches up to the
* package separator (.). So, for example, given the full name one.two.three then one, one.two and one.two.three
* are all matches.
*/
static class TypeNameSet {
private final String[] _names;
public TypeNameSet(Set<String> names) {
_names = names.toArray(new String[names.size()]);
Arrays.sort(_names, String.CASE_INSENSITIVE_ORDER);
}
public void addTo(Collection<String> names) {
names.addAll(Arrays.asList(_names));
}
public List<String> findMatchesFor(String fullName) {
List<String> result = Collections.emptyList();
int index = getIndexOfLastPossibleMatch(fullName);
while (index >= 0 && isPrefix(_names[index], fullName)) {
if (isMatch(_names[index], fullName)) {
if (result.isEmpty()) {
result = new ArrayList<String>();
}
result.add(_names[index]);
}
index--;
}
return result;
}
private int getIndexOfLastPossibleMatch(String fullName) {
int index = Arrays.binarySearch(_names, fullName, String.CASE_INSENSITIVE_ORDER);
if (index < 0) {
int insertionPoint = -index - 1;
index = insertionPoint - 1;
}
return index;
}
private boolean isPrefix(String possiblePrefix, String fullName) {
return fullName.regionMatches(true, 0, possiblePrefix, 0, possiblePrefix.length());
}
private boolean isMatch(String prefix, String fullName) {
return fullName.equalsIgnoreCase(prefix) || fullName.charAt(prefix.length()) == '.';
}
}
@Override
public boolean hasNamespace(String namespace) {
return getAllNamespaces().contains(namespace);
}
@Override
public Set<String> getAllNamespaces() {
if (_namespaces == null) {
try {
_namespaces = TypeSystem.getNamespacesFromTypeNames(getAllTypeNames(), new HashSet<String>());
} catch (NullPointerException e) {
//!! hack to get past dependency issue with tests
return Collections.emptySet();
}
}
return _namespaces;
}
@Override
public void refreshedNamespace(String namespace, IDirectory dir, RefreshKind kind) {
if (_namespaces != null) {
if (kind == RefreshKind.CREATION) {
_namespaces.add(namespace);
} else if (kind == RefreshKind.DELETION) {
_namespaces.remove(namespace);
}
}
}
}