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 au.org.ala.delta.editor.slotfile.Attribute; import au.org.ala.delta.editor.slotfile.DeltaVOP; import au.org.ala.delta.editor.slotfile.TextType; import au.org.ala.delta.editor.slotfile.VOCharBaseDesc; import au.org.ala.delta.editor.slotfile.VOCharBaseDesc.CharTextInfo; import au.org.ala.delta.editor.slotfile.VOCharTextDesc; import au.org.ala.delta.editor.slotfile.VOControllingDesc; import au.org.ala.delta.editor.slotfile.VODirFileDesc; import au.org.ala.delta.editor.slotfile.VODirFileDesc.DirFileFixedData; import au.org.ala.delta.editor.slotfile.VOImageDesc; import au.org.ala.delta.editor.slotfile.VOImageInfoDesc; import au.org.ala.delta.editor.slotfile.VOItemDesc; import au.org.ala.delta.editor.slotfile.model.DirectiveFile.DirectiveType; import au.org.ala.delta.model.AbstractObservableDataSet; import au.org.ala.delta.model.Character; import au.org.ala.delta.model.CharacterDependency; import au.org.ala.delta.model.CharacterType; import au.org.ala.delta.model.Item; import au.org.ala.delta.model.MultiStateAttribute; import au.org.ala.delta.model.MultiStateCharacter; import au.org.ala.delta.model.NumericCharacter; import au.org.ala.delta.model.image.Image; import au.org.ala.delta.model.image.ImageOverlay; import au.org.ala.delta.model.image.ImageSettings; import au.org.ala.delta.model.image.OverlayType; import org.apache.commons.io.FilenameUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Implementation of a DELTA DataSet that uses the SlotFile class to read data * on demand from disk rather than storing it in memory. */ public class SlotFileDataSet extends AbstractObservableDataSet { private DeltaVOP _vop; public SlotFileDataSet(DeltaVOP vop, SlotFileDataSetFactory factory) { super(factory); _vop = vop; } public DeltaVOP getVOP() { return _vop; } public void consistencyCheck() { _vop.consistencyCheck(); } private SlotFileDataSetFactory getFactory() { return (SlotFileDataSetFactory) _factory; } @Override protected Item doGetItem(int number) { synchronized (_vop) { if (number > getMaximumNumberOfItems()) { throw new IndexOutOfBoundsException("No such Item (" + number + ">" + getMaximumNumberOfItems()); } int itemId = _vop.getDeltaMaster().uniIdFromItemNo(number); VOItemDesc itemDesc = (VOItemDesc) _vop.getDescFromId(itemId); VOItemAdaptor adaptor = new VOItemAdaptor(_vop, itemDesc); return new Item(adaptor); } } @Override protected Character doGetCharacter(int number) { synchronized (_vop) { if (number > getNumberOfCharacters()) { throw new IndexOutOfBoundsException("No such Character (" + number + ">" + getNumberOfCharacters()); } int charId = _vop.getDeltaMaster().uniIdFromCharNo(number); VOCharBaseDesc characterDesc = (VOCharBaseDesc) _vop.getDescFromId(charId); return getFactory().wrapCharacter(characterDesc, number); } } @Override public String getAttributeAsString(int itemNumber, int characterNumber) { synchronized (_vop) { int itemId = _vop.getDeltaMaster().uniIdFromItemNo(itemNumber); VOItemDesc itemDesc = (VOItemDesc) _vop.getDescFromId(itemId); int charId = _vop.getDeltaMaster().uniIdFromCharNo(characterNumber); return itemDesc.readAttributeAsText(charId, TextType.UTF8, 1); } } @Override public String getName() { return _vop.getFilename(); } /** * Doesn't do anything - the name is always the filename of the slot file. */ @Override public void setName(String name) { } @Override public int getNumberOfCharacters() { synchronized (_vop) { if (_vop.getDeltaMaster() == null) { return 0; } return _vop.getDeltaMaster().getNChars(); } } @Override public int getMaximumNumberOfItems() { synchronized (_vop) { if (_vop.getDeltaMaster() == null) { return 0; } return _vop.getDeltaMaster().getNItems(); } } /** * Once there are no more observers of this data set, close the underlying * VOP object (which will close files associated with the VOP). */ @Override public void close() { if (_observerList.isEmpty()) { synchronized (_vop) { _vop.close(); } } } @Override protected Character doAddCharacter(int characterNumber, CharacterType type) { synchronized (_vop) { return _factory.createCharacter(type, characterNumber); } } @Override protected Item doAddItem(int itemNumber) { synchronized (_vop) { Item item = _factory.createItem(itemNumber); return item; } } @Override protected Item doAddVariantItem(int parentItemNumber, int itemNumber) { synchronized (_vop) { return _factory.createVariantItem(getItem(parentItemNumber), itemNumber); } } @Override public boolean isModified() { synchronized (_vop) { return _vop.isDirty(); } } @Override public void deleteItem(Item item) { synchronized (_vop) { int itemNumber = item.getItemNumber(); int itemId = _vop.getDeltaMaster().uniIdFromItemNo(itemNumber); VOItemDesc itemDesc = (VOItemDesc) _vop.getDescFromId(itemId); if (itemDesc == null) { return; } List<Image> images = item.getImages(); for (Image image : images) { item.deleteImage(image); } // Remove any references to the item occurring in the directives // "files". int dirFileCount = _vop.getDeltaMaster().getNDirFiles(); for (int i = 1; i <= dirFileCount; ++i) { VODirFileDesc dirFile = (VODirFileDesc) _vop .getDescFromId(_vop.getDeltaMaster().uniIdFromDirFileNo(i)); dirFile.deleteItem(_vop, itemId); } if (_vop.getDeltaMaster().removeItem(itemId)) { _vop.deleteObject(itemDesc); } fireItemDeleted(itemNumber); } } @Override public void moveItem(Item item, int newItemNumber) { synchronized (_vop) { int oldNumber = item.getItemNumber(); _vop.getDeltaMaster().moveItem(oldNumber, newItemNumber); fireItemMoved(item, oldNumber); } } /** * Deletes the supplied character from the SlotFile. Deletion of a character * requires a bit of activity. * * First we will need to delete any item attributes which involve references * to this character, probably after first getting some sort of confirmation * from the user that they really wish to proceed. That confirmation is * assumed to be handled elsewhere. * * Second, we must also delete reference to the character occurring in any * of the internal directives "files" * * Then we also need to delete any "controlling attributes" based on this * character, and update those "controlling attributes" which controlled * this character. * * And finally we need to delete all the text descriptors "owned" by this * character * * When all this is done, then we can finally delete the character itself. * * @param character * the character to delete. */ @Override public void deleteCharacter(Character character) { synchronized (_vop) { int characterNumber = character.getCharacterId(); int characterId = _vop.getDeltaMaster().uniIdFromCharNo(characterNumber); VOCharBaseDesc charDesc = (VOCharBaseDesc) _vop.getDescFromId(characterId); List<Integer> itemList = getEncodedItems(charDesc, VOCharBaseDesc.STATEID_NULL); for (int id : itemList) { VOItemDesc itemDesc = (VOItemDesc) _vop.getDescFromId(id); itemDesc.deleteAttribute(charDesc.getUniId()); } // Delete this character from all directives "files".... int dirFileCount = _vop.getDeltaMaster().getNDirFiles(); for (int i = 1; i <= dirFileCount; ++i) { VODirFileDesc dirFile = (VODirFileDesc) _vop .getDescFromId(_vop.getDeltaMaster().uniIdFromDirFileNo(i)); dirFile.deleteChar(_vop, characterId); } // Delete controlling attributes based on this character... List<Integer> contAttrs = charDesc.readDependentContAttrs(); for (int id : contAttrs) { // Do a bit of consistency checking by making sure the // controlling attribute had // this character as its "owner". VOControllingDesc aContDesc = (VOControllingDesc) _vop.getDescFromId(id); if (aContDesc != null && aContDesc.getCharId() == id) { deleteControlling(aContDesc.getUniId()); } else { throw new IllegalStateException(); } } // Update controlling attributes which controlled this character contAttrs = charDesc.readControllingInfo(); for (int id : contAttrs) { removeDependency((VOControllingDesc) _vop.getDescFromId(id), charDesc.getUniId()); } // Then we need to delete all the text descriptors "owned" by this // character List<CharTextInfo> allText = charDesc.readCharTextInfo(); for (CharTextInfo info : allText) { VOCharTextDesc charText = (VOCharTextDesc) _vop.getDescFromId(info.charDesc); if (charText.getCharBaseId() == characterId) { // Just being // paranoid... _vop.deleteObject(charText); } } // Now delete any associate image descriptors. List<Image> images = character.getImages(); for (Image image : images) { character.deleteImage(image); } // Next remove the character from the master list if (_vop.getDeltaMaster().removeCharacter(characterId)) { _vop.deleteObject(charDesc); fireCharacterDeleted(characterNumber); } else { throw new RuntimeException("Internal Error: Failed to delete character " + characterNumber); } } } // Given a character, fills the vector with the IDs of all items for which // an // attribute has been encoded for the character (if stateId == STATEID_NULL) // or for the state within the character. // Returns "true" if vector is non-empty. private List<Integer> getEncodedItems(VOCharBaseDesc charBase, int stateId) { List<Integer> itemIds = new ArrayList<Integer>(); int charId = charBase.getUniId(); for (int i = 1; i <= getMaximumNumberOfItems(); ++i) { int itemUniId = _vop.getDeltaMaster().uniIdFromItemNo(i); VOItemDesc item = (VOItemDesc) _vop.getDescFromId(itemUniId); if (item.hasAttribute(charId)) { if (stateId != VOCharBaseDesc.STATEID_NULL) { Attribute attr = item.readAttribute(charId); if (!attr.encodesState(charBase, stateId, true)) { continue; } } itemIds.add(itemUniId); } } return itemIds; } @Override public void moveCharacter(Character character, int newCharacterNumber) { synchronized (_vop) { int oldNumber = character.getCharacterId(); _vop.getDeltaMaster().moveCharacter(oldNumber, newCharacterNumber); fireCharacterMoved(character, oldNumber); } } @Override public void deleteCharacterDependency(CharacterDependency characterDependency) { synchronized (_vop) { VOControllingAdapter controlling = (VOControllingAdapter) characterDependency.getImpl(); deleteControlling(controlling.getId()); } } @Override public ImageSettings getImageSettings() { synchronized (_vop) { VOImageInfoDesc imageInfo = _vop.getImageInfo(); if (imageInfo == null) { return null; } ImageSettings settings = new ImageSettings(); ImageSettingsHelper.copyToImageSettings(imageInfo, settings); return settings; } } @Override public void setImageSettings(ImageSettings imageSettings) { synchronized (_vop) { VOImageInfoDesc imageInfo = _vop.getImageInfo(); ImageSettingsHelper.copyFromImageSettings(imageInfo, imageSettings); } } private boolean deleteControlling(int ctlId) { // Get a pointer to the controlling attribute's descriptor, for general // use. VOControllingDesc ctlBase = (VOControllingDesc) _vop.getDescFromId(ctlId); // Next, any characters being controlled by this character will need to // have // their controlling information updated. That is, the dependencies will // need to // be removed. int charId = ctlBase.getCharId(); List<Integer> ctlVector = ctlBase.readControlledChars(); for (int id : ctlVector) { removeDependency(ctlBase, id); } if (ctlBase.getNControlled() != 0) { // Didn't successfully remove all // dependencies!!! throw new RuntimeException("Failed to delete all dependencies"); } // Then remove the attribute from the list of attributes "owned" by it's // character VOCharBaseDesc charBase = (VOCharBaseDesc) _vop.getDescFromId(charId); charBase.removeDependentContAttr(ctlId); // Then remove the controlling attribute from the master list if (_vop.getDeltaMaster().removeContAttr(ctlId)) { // Finally, delete the descriptor from the VOP _vop.deleteObject(ctlBase); return true; } return false; } // Deleting a state is actually a fairly complicated operation. // First we should check to be sure the state is not in use, either // in an item description or in a "controlling attribute" and allow for // user interaction to correct potential problems. For now, we assume this // will be handled elsewhere. However, we will look for // "controlling attributes" // which use this state, and adjust or remove them. // This also needs to be extended to delete any reference to the state from // item descriptions, from internal directives "files", and from image // overlays!!! protected void doDeleteState(MultiStateCharacter character, int stateNumber) { VOCharBaseDesc charDesc = ((VOCharacterAdaptor) character.getImpl()).getCharBaseDesc(); int stateId = charDesc.uniIdFromStateNo(stateNumber); int charId = charDesc.getUniId(); // Find all items that encode this state, and turn the state "off". // This should actually be done by the user before reaching this stage, // but we // do it here anywhere to guarantee consistency. However, at this stage // we would // have to second-guess the user about whether any leading or trailing // comments // should also be deleted.... List<Integer> itemVect = getEncodedItems(charDesc, stateId); if (itemVect.size() > 0) { for (int id : itemVect) { VOItemDesc itemDesc = (VOItemDesc) _vop.getDescFromId(id); deleteStateFromAttribute(itemDesc, charDesc, stateId); } } // Next, make sure that all references to this state are removed from // the directives "files" // (The only non-internal directive which currently uses state id is the // KEY STATES directive...) int dirFileCount = _vop.getDeltaMaster().getNDirFiles(); for (int i = 1; i <= dirFileCount; ++i) { VODirFileDesc dirFile = (VODirFileDesc) _vop.getDescFromId(_vop.getDeltaMaster().uniIdFromDirFileNo(i)); dirFile.deleteState(_vop, charDesc, stateId); } // Now delete any associated image overlays. List<Integer> imageList = charDesc.readImageList(); for (int id : imageList) { VOImageDesc image = (VOImageDesc) _vop.getDescFromId(id); if (image.getOwnerId() == charId) { List<ImageOverlay> overlays = image.readAllOverlays(); for (ImageOverlay overlay : overlays) { if (overlay.isType(OverlayType.OLSTATE) && overlay.stateId == stateId) { deleteImageOverlay(image, overlay.getId(), OverlayType.OLSTATE); } } } } // Should now be possible to use vector of controlled attributes, // rather than scan thru the whole map.... // Check how much of this has already been handled by the descriptor.... List<Integer> contAttrVector = charDesc.readDependentContAttrs(); for (int id : contAttrVector) { VOControllingDesc ctlDesc = (VOControllingDesc) getVOP().getDescFromId(id); if (compare(charId, stateId, ctlDesc)) { List<Integer> ctlStates = ctlDesc.readStateIds(); ctlStates.remove((Integer) stateId); changeControllingStates(ctlDesc, ctlStates); } } if (!charDesc.deleteState(stateId, getVOP())) { throw new RuntimeException( "Unable to delete state: " + stateNumber + " from Character: " + character.getDescription()); } } private void deleteImageOverlay(VOImageDesc desc, int overlayId, int overlayType) { if (overlayType == OverlayType.OLHOTSPOT) { desc.removeLocation(overlayId); } else { desc.removeOverlay(overlayId); } } private void deleteStateFromAttribute(VOItemDesc itemDesc, VOCharBaseDesc charBaseDesc, int stateId) { if (itemDesc == null || charBaseDesc == null) { return; } // Use only for multistate characters. int charId = charBaseDesc.getUniId(); Attribute attr = itemDesc.readAttribute(charId); if (attr != null) { attr.deleteState(charBaseDesc, stateId); } itemDesc.writeAttribute(attr); } private void changeControllingStates(VOControllingDesc controlling, List<Integer> stateIds) { int attrId = controlling.getUniId(); if (stateIds.size() == 0) { deleteControlling(attrId); } // Work around for ISSUE 243 - controlling character descriptors were not being assigned numbers in the // delta master - which leaves existing data sets vulnerable to ISSUE 243 despite the root caused being // addressed. //int attrNo = getVOP().getDeltaMaster().attrNoFromUniId(attrId); //if (attrNo > 0) { List<Integer> oldStateIds = controlling.readStateIds(); Collections.sort(stateIds); if (!stateIds.equals(oldStateIds)) { controlling.writeStateIds(stateIds); } //} } private void removeDependency(VOControllingDesc controlling, int charId) { removeDependency(controlling, (VOCharBaseDesc) _vop.getDescFromId(charId)); } private void removeDependency(VOControllingDesc controlling, VOCharBaseDesc charBase) { int charId = charBase.getUniId(); int attrId = controlling.getUniId(); controlling.removeControlledChar(charId); charBase.removeControllingInfo(attrId); } private boolean compare(int testCharId, int testStateId, VOControllingDesc testDesc) { if (testDesc != null && testDesc.getCharId() == testCharId) { List<Integer> allStates = testDesc.readStateIds(); return allStates.contains(testStateId); } else return false; } /** * MultiState types can be changed into other MultiState types. MultiState * types can only be changed to a non-MultiState type if the only attribute * values that have been coded are comments. * * @param character * the Character to be changed. * @param newType * the type to change the Character to. * @return true if the changeCharacterType operation will succeed. */ private boolean canChangeMultiStateCharacterType(MultiStateCharacter character, CharacterType newType) { if (!newType.isMultistate()) { for (int i = 1; i <= getMaximumNumberOfItems(); i++) { Item item = doGetItem(i); MultiStateAttribute attribute = (MultiStateAttribute) item.getAttribute(character); if (attribute != null && attribute.getPresentStates().size() > 0) { return false; } } } return true; } /** * Numeric types can be changed into other Numeric types. Numeric types can * only be changed to a non-numeric type if the only attribute values that * have been coded are comments. * * @param character * the Character to be changed. * @param newType * the type to change the Character to. * @return true if the changeCharacterType operation will succeed. */ private boolean canChangeNumericCharacterType(NumericCharacter<?> character, CharacterType newType) { if (!newType.isNumeric()) { for (int i = 1; i <= getMaximumNumberOfItems(); i++) { Item item = doGetItem(i); if (item.hasAttribute(character)) { if (!item.getAttribute(character).isComment()) { return false; } } } } return true; } @Override public boolean canChangeCharacterType(Character character, CharacterType newType) { synchronized (_vop) { List<Item> items = getUncodedItems(character); if (items.size() == getMaximumNumberOfItems()) { return true; } if (character.getCharacterType().isMultistate()) { return canChangeMultiStateCharacterType((MultiStateCharacter) character, newType); } else if (character.getCharacterType().isNumeric()) { return canChangeNumericCharacterType((NumericCharacter<?>) character, newType); } return true; } } /** * The SlotFile VOCharBaseDesc only requires minor modification when * changing character type, however a new instance of the model Character * class needs to be created of the correct type. */ @Override public Character changeCharacterType(Character character, CharacterType newType) { synchronized (_vop) { if (!canChangeCharacterType(character, newType)) { throw new IllegalArgumentException( "Cannot change Character type from :" + character.getCharacterType() + " to " + newType); } VOCharacterAdaptor impl = (VOCharacterAdaptor) character.getImpl(); if (character.getCharacterType().isMultistate() && !newType.isMultistate()) { MultiStateCharacter multiStateChar = (MultiStateCharacter) character; while (multiStateChar.getNumberOfStates() > 0) { deleteState(multiStateChar, 1); } } impl.setCharacterType(newType); VOCharBaseDesc charBaseDesc = impl.getCharBaseDesc(); Character newCharacter = getFactory().wrapCharacter(charBaseDesc, character.getCharacterId()); characterTypeChanged(character, newCharacter); return newCharacter; } } @Override public au.org.ala.delta.model.Attribute addAttribute(int itemNumber, int characterNumber) { synchronized (_vop) { Item item = getItem(itemNumber); Character character = getCharacter(characterNumber); au.org.ala.delta.model.Attribute attribute = _factory.createAttribute(character, item); item.addAttribute(character, attribute); return attribute; } } @Override public CharacterDependency addCharacterDependency(MultiStateCharacter owningCharacter, Set<Integer> states, Set<Integer> dependentCharacters) { synchronized (_vop) { CharacterDependency charDependency = _factory.createCharacterDependency(owningCharacter, states, new HashSet<Integer>()); for (int charNum : dependentCharacters) { charDependency.addDependentCharacter(doGetCharacter(charNum)); } return charDependency; } } public DirectiveFile addDirectiveFile(int fileNumber, String fileName, DirectiveType type) { synchronized (_vop) { VODirFileDesc.DirFileFixedData fixed = new VODirFileDesc.DirFileFixedData(); VODirFileDesc dirFile = (VODirFileDesc) _vop.insertObject(fixed, DirFileFixedData.SIZE, null, 0, 0); dirFile.setFileName(fileName); int progType = 0; switch (type) { case CONFOR: progType = VODirFileDesc.PROGTYPE_CONFOR; break; case INTKEY: progType = VODirFileDesc.PROGTYPE_INTKEY; break; case DIST: progType = VODirFileDesc.PROGTYPE_DIST; break; case KEY: progType = VODirFileDesc.PROGTYPE_KEY; break; } dirFile.setProgType((short) progType); _vop.getDeltaMaster().InsertDirFile(dirFile.getUniId(), fileNumber); return new DirectiveFile(dirFile); } } public int getDirectiveFileCount() { synchronized (_vop) { return _vop.getDeltaMaster().getNDirFiles(); } } public DirectiveFile getDirectiveFile(int fileNumber) { synchronized (_vop) { if (fileNumber <= 0 || fileNumber > getDirectiveFileCount()) { throw new IllegalArgumentException("No such file: " + fileNumber); } int id = _vop.getDeltaMaster().uniIdFromDirFileNo(fileNumber); VODirFileDesc dirFile = (VODirFileDesc) _vop.getDescFromId(id); return new DirectiveFile(dirFile); } } /** * Returns the directive file with the specified name or null if it does not * exist. File path information is not used as part of the matching routine. * * @param fileName * the name of the directive file to get. * @return the matching DirectiveFile or null if there is no match. */ public DirectiveFile getDirectiveFile(String fileName) { synchronized (_vop) { // Strip any path information off before doing the match. String tmpFileName = FilenameUtils.getName(fileName); int directiveFileCount = getDirectiveFileCount(); for (int i = 1; i <= directiveFileCount; i++) { DirectiveFile file = getDirectiveFile(i); if (tmpFileName.equals(file.getShortFileName())) { return file; } } return null; } } public void deleteDirectiveFile(DirectiveFile file) { synchronized (_vop) { int id = getVOP().getDeltaMaster().uniIdFromDirFileNo(file.getFileNumber()); // Get a pointer to the file's descriptor, for general use. VODirFileDesc dirFile = (VODirFileDesc) getVOP().getDescFromId(id); // Next remove the file from the master list if (getVOP().getDeltaMaster().removeDirFile(id)) { // Finally, delete the descriptor from the VOP _vop.deleteObject(dirFile); } } } }