package org.objectstyle.wolips.bindings.wod; import java.util.LinkedList; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.objectstyle.wolips.bindings.utils.BindingReflectionUtils; import org.objectstyle.wolips.locate.LocateException; public class BindingValueKeyPath { private IJavaProject _javaProject; private IType _contextType; private String[] _bindingKeyNames; private BindingValueKey[] _bindingKeys; private String _originalKeyPath; private String _validKeyPath; private String _operator; private String _invalidKey; private boolean _valid; private boolean _nsCollection; private boolean _woComponent; private boolean _nsKVC; private boolean _ambiguous; private String _helperFunction; private TypeCache _cache; private Boolean _gettable; private Boolean _settable; public BindingValueKeyPath(String keyPath, IType contextType) throws JavaModelException { this(keyPath, contextType, contextType.getJavaProject(), new TypeCache()); } public BindingValueKeyPath(String keyPath, ITypeOwner typeOwner) throws CoreException, LocateException { this(keyPath, typeOwner.getType(), typeOwner.getType().getJavaProject(), typeOwner.getCache()); } public BindingValueKeyPath(String keyPath, IType contextType, TypeCache cache) throws JavaModelException { this(keyPath, contextType, contextType.getJavaProject(), cache); } public BindingValueKeyPath(String keyPath, IType contextType, IJavaProject javaProject, TypeCache cache) throws JavaModelException { _cache = cache; _javaProject = javaProject; _contextType = contextType; _valid = true; _originalKeyPath = keyPath; boolean isKeyPath = true; // short-circuit for booleans if (keyPath != null && keyPath.length() > 0) { char ch = keyPath.charAt(0); // short-circuit for quoted strings if (ch == '\"' || ch == '\'') { isKeyPath = false; } // short-circuit for numbers else if (Character.isDigit(ch)) { isKeyPath = false; } } if (isKeyPath) { String partialKeyPath; if (keyPath != null) { int pipeIndex = keyPath.indexOf('|'); if (pipeIndex == -1) { partialKeyPath = keyPath; } else { partialKeyPath = keyPath.substring(0, pipeIndex); _helperFunction = keyPath.substring(pipeIndex + 1); } } else { partialKeyPath = ""; } String[] bindingKeyNames; int atIndex = partialKeyPath.indexOf('@'); if (atIndex != -1) { _operator = partialKeyPath.substring(atIndex + 1); partialKeyPath = partialKeyPath.substring(0, atIndex); } bindingKeyNames = partialKeyPath.split("\\."); boolean forceValid = false; if (bindingKeyNames.length > 0) { if (bindingKeyNames[bindingKeyNames.length - 1].endsWith("VALID")) { bindingKeyNames[bindingKeyNames.length - 1] = bindingKeyNames[bindingKeyNames.length - 1].replaceFirst("\\s*//\\s*VALID", ""); forceValid = true; } } // Split tosses empty tokens, so we check to see if we're on the last // "." and fake an empty token in the list if (keyPath != null && keyPath.length() > 0 && keyPath.charAt(keyPath.length() - 1) == '.' && _operator == null && _helperFunction == null) { String[] bindingKeyNamesWithFinalBlank = new String[bindingKeyNames.length + 1]; System.arraycopy(bindingKeyNames, 0, bindingKeyNamesWithFinalBlank, 0, bindingKeyNames.length); bindingKeyNamesWithFinalBlank[bindingKeyNamesWithFinalBlank.length - 1] = ""; _bindingKeyNames = bindingKeyNamesWithFinalBlank; } else { _bindingKeyNames = bindingKeyNames; } if (BindingReflectionUtils.isBooleanValue(keyPath) || "null".equalsIgnoreCase(keyPath) || "nil".equalsIgnoreCase(keyPath)) { _ambiguous = false; _valid = true; _nsKVC = false; _woComponent = false; _nsCollection = false; } else { //System.out.println("BindingValueKeyPath.BindingValueKeyPath: KEYPATH = " + _originalKeyPath); int invalidKeyNum = -1; IType currentType = _contextType; BindingValueKey currentBinding = null; List<BindingValueKey> bindingKeysList = new LinkedList<BindingValueKey>(); for (int keyNum = 0; currentType != null && keyNum < _bindingKeyNames.length; keyNum++) { // we can't verify helper functions or @arrayOps //System.out.println("BindingValueKeyPath.BindingValueKeyPath: checking " + currentType.getFullyQualifiedName() + " " + _bindingKeyNames[keyNum]); boolean keyAccessible = false; List<BindingValueKey> bindingAccessorKeys = cache.getBindingValueAccessorKeys(javaProject, currentType, _bindingKeyNames[keyNum]); if (bindingAccessorKeys.isEmpty() && keyNum == _bindingKeyNames.length - 1) { _gettable = Boolean.FALSE; List<BindingValueKey> bindingMutatorKeys = cache.getBindingValueMutatorKeys(javaProject, currentType, _bindingKeyNames[keyNum]); if (!bindingMutatorKeys.isEmpty()) { keyAccessible = true; _settable = Boolean.TRUE; BindingValueKey bindingKey = bindingMutatorKeys.get(0); bindingKeysList.add(bindingKey); currentType = null; currentBinding = bindingKey; } } else if (!bindingAccessorKeys.isEmpty()) { keyAccessible = true; _gettable = Boolean.TRUE; // NTS: Deal with multiple matches ... BindingValueKey bindingKey = bindingAccessorKeys.get(0); bindingKeysList.add(bindingKey); currentType = bindingKey.getNextType(currentBinding); currentBinding = bindingKey; } if (!keyAccessible) { if (BindingReflectionUtils.isNSKeyValueCoding(currentType, cache) || "java.lang.Object".equals(currentType.getFullyQualifiedName())) { _nsKVC = true; if (BindingReflectionUtils.isNSCollection(currentType, cache)) { _nsCollection = true; _ambiguous = true; invalidKeyNum = keyNum; currentType = null; } else if (BindingReflectionUtils.isWOComponent(currentType, cache)) { _woComponent = true; _ambiguous = true; invalidKeyNum = keyNum; currentType = null; } else { _ambiguous = true; invalidKeyNum = keyNum; currentType = null; } } else { _valid = false; invalidKeyNum = keyNum; currentType = null; } } } if (forceValid) { _valid = true; } // Build the part of the keypath that is valid and the key that is invalid for error reporting ... else if (invalidKeyNum != -1) { StringBuffer validKeyPathBuffer = new StringBuffer(); if (invalidKeyNum > 0) { for (int keyNum = 0; keyNum < invalidKeyNum; keyNum++) { validKeyPathBuffer.append(_bindingKeyNames[keyNum]); validKeyPathBuffer.append("."); } validKeyPathBuffer.setLength(validKeyPathBuffer.length() - 1); } _validKeyPath = validKeyPathBuffer.toString(); _invalidKey = _bindingKeyNames[invalidKeyNum]; } _bindingKeys = bindingKeysList.toArray(new BindingValueKey[bindingKeysList.size()]); } // ... I have no idea why this was here. I wish I had commented it originally //if (!_valid) { //_valid = _bindingKeyNames.length == 1; //} } } public String getOriginalKeyPath() { return _originalKeyPath; } public String getOperator() { return _operator; } public boolean isWOComponent() { return _woComponent; } public boolean isNSKeyValueCoding() { return _nsKVC; } public boolean isNSCollection() { return _nsCollection; } public String getValidKeyPath() { return _validKeyPath; } public String getInvalidKey() { return _invalidKey; } public String getHelperFunction() { return _helperFunction; } public boolean isAmbiguous() { return _ambiguous; } public boolean isValid() { return _valid; } public boolean isGettable() { boolean gettable = false; if (_gettable != null) { gettable = _gettable.booleanValue(); } else { gettable = isValid(); } return gettable; } public boolean isSettable() throws JavaModelException { boolean settable = false; if (_settable != null) { settable = _settable.booleanValue(); } else { BindingValueKey lastBindingKey = getLastBindingKey(); if (lastBindingKey != null) { IMember bindingMember = lastBindingKey.getBindingMember(); if (bindingMember instanceof IField) { settable = true; } else if (bindingMember instanceof IMethod) { IType declaringType = bindingMember.getDeclaringType(); List<BindingValueKey> bindingKeys = _cache.getBindingValueMutatorKeys(_javaProject, declaringType, lastBindingKey.getBindingName()); if (!bindingKeys.isEmpty()) { settable = true; } } } } return settable; } public String getLastBindingKeyName() { String lastBindingKeyName; if (_bindingKeyNames != null && _bindingKeyNames.length > 0) { lastBindingKeyName = _bindingKeyNames[_bindingKeyNames.length - 1]; } else { lastBindingKeyName = null; } return lastBindingKeyName; } public String getNextToLastBindingKeyName() { String nextToLastBindingKeyName; if (_bindingKeyNames.length > 1) { nextToLastBindingKeyName = _bindingKeyNames[_bindingKeyNames.length - 2]; } else { nextToLastBindingKeyName = null; } return nextToLastBindingKeyName; } public BindingValueKey[] getBindingKeys() { return _bindingKeys; } public boolean exists() { return getLastBindingKey() != null; } public boolean isSingleKey() { return getLength() == 1; } public boolean canAddKey() { return isValid() && !exists() && isSingleKey(); } public BindingValueKey getLastBindingKey() { BindingValueKey lastBindingKey; if (_bindingKeys != null && _bindingKeys.length > 0) { lastBindingKey = _bindingKeys[_bindingKeys.length - 1]; } else { lastBindingKey = null; } return lastBindingKey; } public BindingValueKey getNextToLastBindingKey() { BindingValueKey nextToLastBindingKey; if (_bindingKeys.length > 1) { nextToLastBindingKey = _bindingKeys[_bindingKeys.length - 2]; } else { nextToLastBindingKey = null; } return nextToLastBindingKey; } public IType getLastType() throws JavaModelException { BindingValueKey lastBindingKey = getLastBindingKey(); IType lastType; if (lastBindingKey != null) { lastType = lastBindingKey.getNextType(getNextToLastBindingKey()); } else { lastType = _contextType; } return lastType; } public List<BindingValueKey> getPartialMatchesForLastBindingKey(boolean allowInheritanceDuplicates) throws JavaModelException { List<BindingValueKey> bindingKeysList = null; if (_helperFunction != null) { // if there's a helper function, we can't do completion } else if (_operator != null) { bindingKeysList = BindingReflectionUtils.getBindingKeys(_javaProject, getLastType(), "@" + _operator, false, BindingReflectionUtils.ACCESSORS_ONLY, allowInheritanceDuplicates, _cache); } else if (_bindingKeys == null) { bindingKeysList = BindingReflectionUtils.getBindingKeys(_javaProject, getLastType(), _originalKeyPath, false, BindingReflectionUtils.ACCESSORS_ONLY, allowInheritanceDuplicates, _cache); } else { String partialBindingKeyName; BindingValueKey lastBindingKey; if (_bindingKeyNames.length == _bindingKeys.length) { partialBindingKeyName = getLastBindingKeyName(); lastBindingKey = getNextToLastBindingKey(); } else { partialBindingKeyName = getLastBindingKeyName(); lastBindingKey = getLastBindingKey(); } IType lastType; if (lastBindingKey == null) { lastType = _contextType; } else { lastType = lastBindingKey.getNextType(getNextToLastBindingKey()); } if (lastType != null) { // Jump forward to the last '.' and look for valid "get" method // completion proposals based on the partial token bindingKeysList = BindingReflectionUtils.getBindingKeys(_javaProject, lastType, partialBindingKeyName, false, BindingReflectionUtils.ACCESSORS_ONLY, allowInheritanceDuplicates, _cache); } } return bindingKeysList; } public int getLength() { return _bindingKeyNames == null ? 1 : _bindingKeyNames.length; } }