/* * Copyright 2010-2011 Øyvind Berg (elacin@gmail.com) * * 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.elacin.pdfextract.tree; import org.elacin.pdfextract.geom.MathUtils; import org.elacin.pdfextract.style.Style; import org.elacin.pdfextract.style.TextUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.Serializable; import java.util.*; /** * Created by IntelliJ IDEA. User: elacin Date: Mar 18, 2010 Time: 3:16:53 PM To change this * template use File | Settings | File Templates. */ public abstract class AbstractParentNode<ChildType extends AbstractNode, ParentType extends AbstractParentNode> extends AbstractNode<ParentType> { // ------------------------------ FIELDS ------------------------------ /* caches */ @Nullable protected transient String textCache; @Nullable protected transient Style styleCache; @Nullable protected transient String toStringCache; /* children nodes */ @NotNull private final List<ChildType> children = new ArrayList<ChildType>(); // --------------------------- CONSTRUCTORS --------------------------- public AbstractParentNode() {} public AbstractParentNode(@NotNull final ChildType child) { addChild(child); } // ------------------------ INTERFACE METHODS ------------------------ // --------------------- Interface HasPosition --------------------- public void calculatePos() { setPos(MathUtils.findBounds(children)); } // --------------------- Interface StyledText --------------------- @NotNull public Style getStyle() { if (styleCache == null) { styleCache = TextUtils.findDominatingStyle(children); } return styleCache; } @NotNull @Override public String getText() { if (textCache == null) { StringBuilder sb = new StringBuilder(); if (!children.isEmpty()) { for (ChildType child : children) { sb.append(child.getText()).append(" "); } sb.setLength(sb.length() - 1); } textCache = sb.toString(); } return textCache; } // ------------------------ CANONICAL METHODS ------------------------ @NotNull @Override public String toString() { if (toStringCache == null) { final StringBuilder sb = new StringBuilder(); sb.append(getText()); toStringCache = sb.toString(); } return toStringCache; } // ------------------------ OVERRIDING METHODS ------------------------ @SuppressWarnings({ "unchecked" }) /* wtf */ @NotNull public EnumSet<Role> getRoles() { EnumSet<Role> ret = EnumSet.noneOf(Role.class); for (ChildType child : children) { ret.addAll(child.getRoles()); } return ret; } // --------------------- GETTER / SETTER METHODS --------------------- @NotNull public List<ChildType> getChildren() { return children; } // -------------------------- PUBLIC METHODS -------------------------- public final void addChild(@NotNull final ChildType child) { child.invalidateThisAndParents(); children.add(child); child.parent = this; child.invalidateThisAndParents(); Collections.sort(children, getChildComparator()); } public final void addChildren(@NotNull final List<ChildType> newChildren) { for (ChildType child : newChildren) { child.invalidateThisAndParents(); children.add(child); child.parent = this; } Collections.sort(children, getChildComparator()); invalidateThisAndParents(); } @NotNull public abstract Comparator<ChildType> getChildComparator(); public void removeChild(ChildType child) { child.invalidateThisAndParents(); children.remove(child); child.parent = null; } public void removeChildren(List<ChildType> childrenToRemove) { for (ChildType child : childrenToRemove) { child.invalidateThisAndParents(); children.remove(child); child.parent = null; } } // -------------------------- OTHER METHODS -------------------------- protected void invalidateThisAndParents() { invalidatePos(); textCache = null; toStringCache = null; styleCache = null; if (getParent() != null) { getParent().invalidateThisAndParents(); } } // -------------------------- INNER CLASSES -------------------------- /** * This comparator will compare two nodes based on their position within a page TODO: add page * number here */ protected class StandardNodeComparator implements Comparator<ChildType>, Serializable { private static final long serialVersionUID = 3903290320365277004L; public int compare(@NotNull final ChildType o1, @NotNull final ChildType o2) { if (o1.getPos().y < o2.getPos().y) { return -1; } else if (o1.getPos().y > o2.getPos().y) { return 1; } if (o1.getPos().x < o2.getPos().x) { return -1; } else if (o1.getPos().x > o2.getPos().x) { return 1; } return 0; } } }