/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.icepdf.core.pobjects; import org.icepdf.core.util.Library; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * NameNode used in building a name tree. * * @since 4.0 */ public class NameNode extends Dictionary { public static final Name KIDS_KEY = new Name("Kids"); public static final Name NAMES_KEY = new Name("Names"); public static final Name LIMITS_KEY = new Name("Limits"); private static Object NOT_FOUND = new Object(); private static Object NOT_FOUND_IS_LESSER = new Object(); private static Object NOT_FOUND_IS_GREATER = new Object(); private boolean namesAreDecrypted; // flat tree, names and values only. private List<String> namesAndValues; // kids type of tree, need to build out the structure private List kidsReferences; private List<NameNode> kidsNodes; private String lowerLimit; private String upperLimit; /** * @param l * @param h */ @SuppressWarnings("unchecked") public NameNode(Library l, HashMap h) { super(l, h); // root node can either be a Kids or Names Object o = library.getObject(entries, KIDS_KEY); if (o != null && o instanceof List) { // we have a kids array which can be composed of an intermediary // /limits/kids and or the leaf /limits/names kidsReferences = (List) o; int sz = kidsReferences.size(); if (sz > 0) { kidsNodes = new ArrayList<NameNode>(sz); for (Object ref : kidsReferences) { if (ref instanceof Reference) { o = library.getObject((Reference) ref); kidsNodes.add(new NameNode(library, (HashMap) o)); } } } } // if no kids[] then we must have a names array which is only one leaf. else if (o == null) { // process the names namesAreDecrypted = false; o = library.getObject(entries, NAMES_KEY); if (o != null && o instanceof List) { namesAndValues = (List) o; } } // assign the upper and lower limits if any. o = library.getObject(entries, LIMITS_KEY); if (o != null && o instanceof List) { List limits = (List) o; if (limits.size() >= 2) { lowerLimit = decryptIfText(limits.get(0)); upperLimit = decryptIfText(limits.get(1)); } } } public boolean isEmpty() { return kidsNodes.size() == 0; } public boolean hasLimits() { return library.getObject(entries, LIMITS_KEY) != null; } public List getNamesAndValues() { return namesAndValues; } public List getKidsReferences() { return kidsReferences; } public List<NameNode> getKidsNodes() { return kidsNodes; } public String getLowerLimit() { return lowerLimit; } public String getUpperLimit() { return upperLimit; } private void ensureNamesDecrypted() { if (namesAreDecrypted) return; namesAreDecrypted = true; // We need to look at each key and encrypt any Text objects which // is every second object for (int i = 0; i < namesAndValues.size(); i += 2) { namesAndValues.set(i, decryptIfText(namesAndValues.get(i))); } } /** * Decyptes the node String object and returns a String value of the node * which is used to find names in the name tree. We only do this once * for the notes names vector. * * @param tmp object to decrypt. * @return decrypted string. */ private String decryptIfText(Object tmp) { if (tmp instanceof StringObject) { StringObject nameText = (StringObject) tmp; return nameText.getDecryptedLiteralString(library.getSecurityManager()); } else if (tmp instanceof String) { return (String) tmp; } return null; } /** * Search for the given name in the name tree. * * @param name name to search for * @return retrieved object if any otherwise, null. */ Object searchName(String name) { Object ret = search(name); if (ret == NOT_FOUND || ret == NOT_FOUND_IS_LESSER || ret == NOT_FOUND_IS_GREATER) { ret = null; } return ret; } private Object search(String name) { //System.out.println("search() for: " + name + " lowerLimit: " + lowerLimit + " upperLimit: " + upperLimit + " " + name); if (kidsReferences != null) { //System.out.print("search() kids ... "); if (lowerLimit != null) { int cmp = lowerLimit.compareTo(name); if (cmp > 0) { //System.out.println("skLESSER"); return NOT_FOUND_IS_LESSER; } else if (cmp == 0) return getNode(0).search(name); } if (upperLimit != null) { int cmp = upperLimit.compareTo(name); if (cmp < 0) { //System.out.println("skGREATER"); return NOT_FOUND_IS_GREATER; } else if (cmp == 0) return getNode(kidsReferences.size() - 1).search(name); } //System.out.println("skBETWEEN"); return binarySearchKids(0, kidsReferences.size() - 1, name); } else if (namesAndValues != null) { //System.out.print("search() names ... "); int numNamesAndValues = namesAndValues.size(); if (lowerLimit != null) { int cmp = lowerLimit.compareTo(name); if (cmp > 0) { //System.out.println("snLESSER"); return NOT_FOUND_IS_LESSER; } else if (cmp == 0) { ensureNamesDecrypted(); if (namesAndValues.get(0).equals(name)) { Object ob = namesAndValues.get(1); if (ob instanceof Reference) ob = library.getObject((Reference) ob); return ob; } } } if (upperLimit != null) { int cmp = upperLimit.compareTo(name); if (cmp < 0) { //System.out.println("snGREATER"); return NOT_FOUND_IS_GREATER; } else if (cmp == 0) { ensureNamesDecrypted(); if (namesAndValues.get(numNamesAndValues - 2).equals(name)) { Object ob = namesAndValues.get(numNamesAndValues - 1); if (ob instanceof Reference) ob = library.getObject((Reference) ob); return ob; } } } //System.out.println("snBETWEEN"); ensureNamesDecrypted(); Object ret = binarySearchNames(0, numNamesAndValues - 1, name); if (ret == NOT_FOUND || ret == NOT_FOUND_IS_LESSER || ret == NOT_FOUND_IS_GREATER) ret = null; return ret; } return null; } private Object binarySearchKids(int firstIndex, int lastIndex, String name) { if (firstIndex > lastIndex) return NOT_FOUND; int pivot = firstIndex + ((lastIndex - firstIndex) / 2); Object ret = getNode(pivot).search(name); //System.out.print("binarySearchKids [ " + firstIndex + ", " + lastIndex + " ] pivot: " + pivot + " name: " + name + " ... "); if (ret == NOT_FOUND_IS_LESSER) { //System.out.println("kLESSER"); return binarySearchKids(firstIndex, pivot - 1, name); } else if (ret == NOT_FOUND_IS_GREATER) { //System.out.println("kGREATER"); return binarySearchKids(pivot + 1, lastIndex, name); } else if (ret == NOT_FOUND) { //System.out.println("kNOT FOUND"); // This shouldn't happen, so is either a bug, or a miss coded PDF file for (int i = firstIndex; i <= lastIndex; i++) { if (i == pivot) continue; Object r = getNode(i).search(name); if (r != NOT_FOUND && r != NOT_FOUND_IS_LESSER && r != NOT_FOUND_IS_GREATER) { ret = r; break; } } } return ret; } private Object binarySearchNames(int firstIndex, int lastIndex, String name) { if (firstIndex > lastIndex) return NOT_FOUND; int pivot = firstIndex + ((lastIndex - firstIndex) / 2); pivot &= 0xFFFFFFFE; // Clear LSB to ensure even index //System.out.print("binarySearchNames [ " + firstIndex + ", " + lastIndex + " ] pivot: " + pivot + " size: " + namesAndValues.size() + " compare " + name + " to " + namesAndValues.get(pivot).toString() + " ... "); int cmp = namesAndValues.get(pivot).compareTo(name); if (cmp == 0) { //System.out.println("nEQUAL"); Object ob = namesAndValues.get(pivot + 1); if (ob instanceof Reference) ob = library.getObject((Reference) ob); return ob; } else if (cmp > 0) { //System.out.println("nLESSER"); return binarySearchNames(firstIndex, pivot - 1, name); } else if (cmp < 0) { //System.out.println("nGREATER"); return binarySearchNames(pivot + 2, lastIndex, name); } return NOT_FOUND; } public NameNode getNode(int index) { NameNode n = kidsNodes.get(index); if (n == null) { Reference r = (Reference) kidsReferences.get(index); HashMap nh = (HashMap) library.getObject(r); n = new NameNode(library, nh); kidsNodes.set(index, n); } return n; } }