Java tutorial
/******************************************************************************* * Copyright (C) 2011 Atlas of Living Australia * All Rights Reserved. * * The contents of this file are subject to the Mozilla Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. ******************************************************************************/ package au.org.ala.delta.editor.slotfile.model; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import au.org.ala.delta.editor.slotfile.*; import au.org.ala.delta.model.attribute.ChunkType; import org.apache.commons.lang.NotImplementedException; import au.org.ala.delta.model.CharacterDependency; import au.org.ala.delta.model.CharacterType; import au.org.ala.delta.model.CircularDependencyException; import au.org.ala.delta.model.Item; import au.org.ala.delta.model.impl.CharacterData; import au.org.ala.delta.model.impl.ControllingInfo; import au.org.ala.delta.model.impl.ControllingInfo.ControlledStateType; /** * Adapts the CharacterData interface to the VOCharBaseDesc and VOCharTextDesc * slot file classes. */ public class VOCharacterAdaptor extends ImageHolderAdaptor implements CharacterData { /** * If they've been specified, units are stored as state text for state * number 1. */ private static final int UNITS_TEXT_STATE_NUMBER = 1; private static final int VOUID_NULL = 0; private VOCharBaseDesc _charDesc; private VOCharTextDesc _textDesc; private DeltaVOP _vop; public VOCharacterAdaptor(DeltaVOP vop, VOCharBaseDesc charBase, VOCharTextDesc textDesc) { _vop = vop; _charDesc = charBase; _textDesc = textDesc; } @Override public int getNumber() { return _vop.getDeltaMaster().charNoFromUniId(_charDesc.getUniId()); } public void setNumber(int number) { // NOP - numbers are managed by the VOP throw new RuntimeException("InternalError: setNumber called on VOCharacterAdapter"); } @Override public String getDescription() { synchronized (_vop) { String description = ""; if (_textDesc != null) { description = _textDesc.readFeatureText(TextType.RTF); } return description; } } @Override public void setDescription(String desc) { synchronized (_vop) { _textDesc.makeTemp(); _textDesc.writeFeatureText(desc); } } @Override public void setUnits(String units) { synchronized (_vop) { _charDesc.setInitialStateNumber(1); int stateId = _charDesc.uniIdFromStateNo(1); _textDesc.makeTemp(); _textDesc.writeStateText(units, stateId); } } @Override public boolean isExclusive() { synchronized (_vop) { return _charDesc.testCharFlag(VOCharBaseDesc.CHAR_EXCLUSIVE); } } @Override public void setExclusive(boolean b) { synchronized (_vop) { if (b) { _charDesc.setCharFlag(VOCharBaseDesc.CHAR_EXCLUSIVE); } else { _charDesc.clearCharFlag(VOCharBaseDesc.CHAR_EXCLUSIVE); } } } @Override public boolean isMandatory() { synchronized (_vop) { return _charDesc.testCharFlag(VOCharBaseDesc.CHAR_MANDATORY); } } @Override public void setMandatory(boolean b) { synchronized (_vop) { if (b) { _charDesc.setCharFlag(VOCharBaseDesc.CHAR_MANDATORY); } else { _charDesc.clearCharFlag(VOCharBaseDesc.CHAR_MANDATORY); } } } public VOCharBaseDesc getCharBaseDesc() { return _charDesc; } @Override public String getUnits() { synchronized (_vop) { String units = ""; if (_charDesc.getNStatesUsed() >= UNITS_TEXT_STATE_NUMBER) { units = getStateText(UNITS_TEXT_STATE_NUMBER); } return units; } } @Override public String getStateText(int stateNumber) { synchronized (_vop) { if (_textDesc == null) { return ""; } int stateId = _charDesc.uniIdFromStateNo(stateNumber); return _textDesc.readStateText(stateId, TextType.RTF); } } @Override public int getNumberOfStates() { synchronized (_vop) { // Trying to read past the number of states actually used yields an // error return _charDesc.getNStatesUsed(); } } @Override public void setStateText(int stateNumber, String text) { synchronized (_vop) { int stateId = _charDesc.uniIdFromStateNo(stateNumber); _textDesc.writeStateText(text, stateId); } } @Override public void setNumberOfStates(int numStates) { synchronized (_vop) { if (_charDesc.getNStatesUsed() > 0) { throw new NotImplementedException("Ooops, don't currently handle deleting existing states..."); } _charDesc.setInitialStateNumber(numStates); } } @Override public String getNotes() { synchronized (_vop) { return _textDesc.readNoteText(TextType.RTF); } } @Override public void setNotes(String note) { synchronized (_vop) { _textDesc.writeNoteText(note); } } @Override public int getCodedImplicitState() { synchronized (_vop) { return _charDesc.stateNoFromUniId(_charDesc.getCodedImplicit()); } } @Override public int getUncodedImplicitState() { synchronized (_vop) { return _charDesc.stateNoFromUniId(_charDesc.getUncodedImplicit()); } } @Override public void setCodedImplicitState(int stateNo) { synchronized (_vop) { int stateId = _charDesc.uniIdFromStateNo(stateNo); _charDesc.setCodedImplicit((short) stateId); } } @Override public void setUncodedImplicitState(int stateNo) { synchronized (_vop) { int stateId = _charDesc.uniIdFromStateNo(stateNo); _charDesc.setUncodedImplicit((short) stateId); } } @Override public void validateAttributeText(String text, ControllingInfo controlled) { synchronized (_vop) { Attribute attribute = new Attribute(text, _charDesc); if (controlled.isInapplicable()) { for (AttrChunk chunk : attribute) { int chunkType = chunk.getType(); if (chunkType != ChunkType.CHUNK_INAPPLICABLE && chunkType != ChunkType.CHUNK_TEXT && chunkType != ChunkType.CHUNK_LONGTEXT && // If "Unknown or inapplicable, allow Unknown and OR. !((chunkType == ChunkType.CHUNK_UNKNOWN || chunkType == ChunkType.CHUNK_OR) && controlled.isStrictlyInapplicable())) { throw new Attribute.AttributeParseException( Attribute.AttributeParseError.EAP_IS_INAPPLICABLE, -1); } } } } } class CompareCharNos implements Comparator<Integer> { private boolean _compareStates; public CompareCharNos(boolean compareStates) { _compareStates = compareStates; } @Override public int compare(Integer leftNo, Integer rightNo) { VOControllingDesc left = getDescFromId(leftNo); VOControllingDesc right = getDescFromId(rightNo); return compare(left, right); } public int compare(VOControllingDesc left, VOControllingDesc right) { int leftNo = ((DeltaVOP) _charDesc.getVOP()).getDeltaMaster().charNoFromUniId(left.getCharId()); int rightNo = ((DeltaVOP) _charDesc.getVOP()).getDeltaMaster().charNoFromUniId(right.getCharId()); if (leftNo == rightNo && _compareStates) { throw new NotImplementedException(); } else { return leftNo - rightNo; } } } @SuppressWarnings("unchecked") protected <T extends VOAnyDesc> T getDescFromId(int uniId) { return (T) ((DeltaVOP) _charDesc.getVOP()).getDescFromId(uniId); } protected VODeltaMasterDesc getDeltaMaster() { return ((DeltaVOP) _charDesc.getVOP()).getDeltaMaster(); } @Override public ControllingInfo checkApplicability(Item itemModel) { synchronized (_vop) { VOItemDesc item = ((VOItemAdaptor) itemModel.getItemData()).getItemDesc(); return checkApplicability(item, _charDesc, 0, new ArrayList<Integer>()); } } protected ControllingInfo checkApplicability(VOItemDesc item, VOCharBaseDesc charBase, int recurseLevel, List<Integer> testedControlledChars) { int controllingId = 0; if (item == null || charBase == null || charBase.getNControlling() == 0) { return new ControllingInfo(); } boolean unknownOk = false; List<Integer> controlling = charBase.readControllingInfo(); if (controlling != null && controlling.size() > 1) { Collections.sort(controlling, new CompareCharNos(false)); } List<Integer> controllingChars = new ArrayList<Integer>(); SortedSet<Integer> controllingStates = new TreeSet<Integer>(); List<Integer> newContStates = new ArrayList<Integer>(); int testCharId = VOUID_NULL; controlling.add(VOUID_NULL); // Append dummy value, to ease handling of // the last element // Loop through all controlling attributes which directly control this // character... for (Integer i : controlling) { int newContCharId = 0; if (i == VOUID_NULL) { newContCharId = VOUID_NULL; } else { VOControllingDesc contAttrDesc = (VOControllingDesc) ((DeltaVOP) charBase.getVOP()) .getDescFromId(i); newContStates = contAttrDesc.readStateIds(); Collections.sort(newContStates); newContCharId = contAttrDesc.getCharId(); } if (newContCharId == testCharId) { // / Build up all relevant controlling attributes under the // control of a // / single controlling character, merging the state lists as we // go.... controllingStates.addAll(newContStates); } else { // Do checks when changing controlling state if (testCharId != VOUID_NULL) { VOCharBaseDesc testCharBase = getDescFromId(testCharId); if (!CharType.isMultistate(testCharBase.getCharType())) { throw new RuntimeException("Controlling characters must be multistate!"); } controllingId = testCharId; // If the controlling character is coded, see whether it // makes us inapplicable if (item.hasAttribute(controllingId)) { Attribute attrib = item.readAttribute(controllingId); List<Integer> codedStates = new ArrayList<Integer>(); short[] pseudoValues = new short[] { 0 }; attrib.getEncodedStates(testCharBase, codedStates, pseudoValues); // If controlling character is "variable", we are NOT // controlled if ((pseudoValues[0] & VOItemDesc.PSEUDO_VARIABLE) == 0) { if (codedStates.isEmpty()) { // If there are no states for the controlling // character, // but it is explicitly coded with the "unknown" // pseudo-value, // allow the controlled character to also be // unknown. if ((pseudoValues[0] & VOItemDesc.PSEUDO_UNKNOWN) != 0) { unknownOk = true; } else { return new ControllingInfo(ControlledStateType.Inapplicable, controllingId); } } else if (controllingStates.containsAll(codedStates)) { return new ControllingInfo(ControlledStateType.Inapplicable, controllingId); } } } else if (testCharBase.getUncodedImplicit() != VOCharBaseDesc.STATEID_NULL) { // if the controlling character is not encoded, see if // there is an implicit value for it if (controllingStates.contains(testCharBase.getUncodedImplicit())) { return new ControllingInfo(ControlledStateType.Inapplicable, controllingId); } } else { return new ControllingInfo(ControlledStateType.Inapplicable, controllingId); // /// This should probably be handled as a somewhat // special case, // /// so the user can be pointed in the right direction } } controllingId = VOUID_NULL; testCharId = newContCharId; if (testCharId != VOUID_NULL) { controllingChars.add(testCharId); } controllingStates.clear(); controllingStates.addAll(newContStates); } } // Up to this point, nothing has made this character inapplicable. // But it is possible that one of the controlling characters has itself // been made inapplicable. // Is this check really necessary? I suppose it is, but it slows things // down... for (int j : controllingChars) { if (++recurseLevel >= getDeltaMaster().getNContAttrs()) { try { List<Integer> contChars = new ArrayList<Integer>(); getControlledChars(testedControlledChars, _charDesc, contChars, true); } catch (CircularDependencyException ex) { return new ControllingInfo(ControlledStateType.Inapplicable, controllingId); } } VOCharBaseDesc testCharBase = getDescFromId(j); ControllingInfo info = checkApplicability(item, testCharBase, recurseLevel, testedControlledChars); if (info.isInapplicable()) { return info; } } return unknownOk ? new ControllingInfo(ControlledStateType.InapplicableOrUnknown, controllingId) : new ControllingInfo(); } @Override public List<Integer> getControlledCharacterNumbers(boolean indirect) { synchronized (_vop) { List<Integer> controlledCharacterIds = new ArrayList<Integer>(); List<Integer> testedCharacterIds = new ArrayList<Integer>(); getControlledChars(testedCharacterIds, _charDesc, controlledCharacterIds, indirect); List<Integer> controlledCharacterNos = new ArrayList<Integer>(); for (int id : controlledCharacterIds) { controlledCharacterNos.add(getVOP().getDeltaMaster().charNoFromUniId(id)); } return controlledCharacterNos; } } private boolean getControlledChars(List<Integer> testedControlling, VOCharBaseDesc charBase, List<Integer> contChars, boolean includeIndirect) { return getControlledChars(testedControlling, charBase, contChars, includeIndirect, 0); } private boolean getControlledChars(List<Integer> testedControlling, VOCharBaseDesc charBase, List<Integer> contChars, boolean includeIndirect, int baseId) { // We maintain a list of "controlling" characters that have been (or, // rather, are being) // tested. This can prevent the infinite recursion which can otherwise // result if "circular" dependencies somehow are formed. if (testedControlling.contains(charBase.getUniId())) { return false; } else { testedControlling.add(charBase.getUniId()); List<Integer> contAttrVector = charBase.readDependentContAttrs(); if (contAttrVector.size() > 0) { // / Loop though all the controlling attributes "owned" by this // character for (Integer iter : contAttrVector) { VOControllingDesc contAttrDesc = getDescFromId(iter); List<Integer> controlledChars = contAttrDesc.readControlledChars(); contChars.addAll(controlledChars); } if (contChars.contains(charBase.getUniId()) || contChars.contains(baseId)) { throw new CircularDependencyException(); } if (includeIndirect) { // OK. We now have a list of all characters DIRECTLY // controlled // by this one. We should add all those INDIRECTLY // controlled as well. List<Integer> contDirect = new ArrayList<Integer>(contChars); for (Integer i : contDirect) { List<Integer> contIndirect = new ArrayList<Integer>(); VOCharBaseDesc indirCharBase = getDescFromId(i); if (getControlledChars(testedControlling, indirCharBase, contIndirect, true, baseId)) { if (contIndirect.contains(baseId)) { throw new CircularDependencyException(); } contChars.addAll(contIndirect); } } } } return !contChars.isEmpty(); } } @Override public void addDependentCharacters(CharacterDependency dependency) { synchronized (_vop) { VOControllingAdapter impl = (VOControllingAdapter) dependency.getImpl(); _charDesc.addControllingInfo(impl.getId()); } } @Override public List<CharacterDependency> getDependentCharacters() { synchronized (_vop) { List<Integer> controllingInfoIds = _charDesc.readDependentContAttrs(); return idListToCharacterDependencyList(controllingInfoIds); } } /** * Creates and returns a CharacterDependency populated from the supplied * VOControllingDesc. * * @param controllingDesc * the source of data for the CharacterDependency. * @return a new CharacterDependency. */ private CharacterDependency characterDependencyFromControllingInfo(VOControllingDesc controllingDesc) { return new CharacterDependency(new VOControllingAdapter(getVOP(), controllingDesc)); } @Override public void addControllingCharacters(CharacterDependency dependency) { synchronized (_vop) { int id = VOControllingAdapter.getId(dependency); _charDesc.addControllingInfo(id); } } @Override public void removeControllingCharacter(CharacterDependency dependency) { synchronized (_vop) { int id = VOControllingAdapter.getId(dependency); _charDesc.removeControllingInfo(id); } } @Override public List<CharacterDependency> getControllingCharacters() { synchronized (_vop) { List<Integer> controllingInfoIds = _charDesc.readControllingInfo(); return idListToCharacterDependencyList(controllingInfoIds); } } private List<CharacterDependency> idListToCharacterDependencyList(List<Integer> controllingInfoIds) { List<CharacterDependency> charDependencyList = new ArrayList<CharacterDependency>(); for (int id : controllingInfoIds) { VOControllingDesc controllingDesc = (VOControllingDesc) getDescFromId(id); CharacterDependency dependency = characterDependencyFromControllingInfo(controllingDesc); charDependencyList.add(dependency); } return charDependencyList; } @Override public void addState(int stateNumber) { synchronized (_vop) { _charDesc.insertState(stateNumber, getVOP()); } } @Override public void moveState(int stateNumber, int newNumber) { synchronized (_vop) { _charDesc.moveState(stateNumber, newNumber); } } /** * Changes the character type. If the Character is a multistate Character, * all of the states will be deleted before changing the type. * * @param newType * the new type for this character. */ public void setCharacterType(CharacterType newType) { synchronized (_vop) { if (CharType.isMultistate(_charDesc.getCharType()) && (!newType.isMultistate())) { _charDesc.setCodedImplicit(VOCharBaseDesc.STATEID_NULL); _charDesc.setUncodedImplicit(VOCharBaseDesc.STATEID_NULL); } _charDesc.setCharType(CharacterTypeConverter.toCharType(newType)); } } @Override public float getReliability() { throw new NotImplementedException(); } @Override public void setReliability(float reliability) { throw new NotImplementedException(); } @Override public int getMaximumValue() { // Always return 0, maximum value for a character // is not stored in slot file return 0; } @Override public void setMaximumValue(int max) { // Do nothing, maximum value for a character is not stored in slot file. } @Override public int getMinimumValue() { // Always return 0, minimum value for a character // is not stored in slot file return 0; } @Override public void setMinimumValue(int min) { // Do nothing, maximum value for a character is not stored in slot file. } @Override public String getItemSubheading() { throw new NotImplementedException(); } @Override public void setItemSubheading(String charItemSubheading) { throw new NotImplementedException(); } @Override public List<Float> getKeyStateBoundaries() { throw new NotImplementedException(); } @Override public void setKeyStateBoundaries(List<Float> keyStateBoundaries) { throw new NotImplementedException(); } @Override public boolean getContainsSynonmyInformation() { throw new NotImplementedException(); } @Override public void setContainsSynonmyInformation(boolean containsSynonmyInfo) { throw new NotImplementedException(); } @Override public boolean getOmitOr() { throw new NotImplementedException(); } @Override public void setOmitOr(boolean omitOr) { throw new NotImplementedException(); } @Override public boolean getUseCc() { throw new NotImplementedException(); } @Override public void setUseCc(boolean useCc) { throw new NotImplementedException(); } @Override public boolean getOmitPeriod() { throw new NotImplementedException(); } @Override public void setOmitPeriod(boolean omitPeriod) { throw new NotImplementedException(); } @Override public boolean getNewParagraph() { throw new NotImplementedException(); } @Override public void setNewParagraph(boolean newParagraph) { throw new NotImplementedException(); } @Override public boolean getNonAutoCc() { throw new NotImplementedException(); } @Override public void setNonAutoCc(boolean nonAutoCc) { throw new NotImplementedException(); } @Override protected DeltaVOP getVOP() { return _vop; } @Override protected VOImageHolderDesc getImageHolder() { return _charDesc; } @Override public boolean isIntegerRepresentedAsReal() { throw new NotImplementedException(); } @Override public void setIntegerRepresentedAsReal(boolean isIntegerRepresentedAsReal) { throw new NotImplementedException(); } }