Java tutorial
/******************************************************************************* * Copyright 2013 Ivan Shubin http://mindengine.net * * 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 net.mindengine.blogix.web.tiles; import java.io.File; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.commons.io.FileUtils; import org.apache.commons.io.LineIterator; public class TilesContainer { private Map<String, Tile> tiles; public void load(File file) throws IOException { TileLine rootTileLine = readAllTileLines(file); tiles = new HashMap<String, Tile>(); if (rootTileLine.children != null) { for (TileLine childTile : rootTileLine.children) { Tile tile = convertTile(childTile); tiles.put(tile.getName(), tile); } } } private Tile convertTile(TileLine tileLine) { Tile tile = new Tile(); tile.setName(tileLine.key); tile.setTiles(new HashMap<String, Tile>()); if (tileLine.isExtending()) { tile.setExtendsTile(tileLine.getExtendedBaseName()); tile.setTiles(new HashMap<String, Tile>()); extendTile(tile, tileLine, new LinkedList<String>()); } else { tile.setValue(tileLine.value); } if (tileLine.children != null) { for (TileLine childTileLine : tileLine.children) { Tile childTile = convertTile(childTileLine); tile.getTiles().put(childTile.getName(), childTile); } } return tile; } private void extendTile(Tile tile, TileLine lookOutsideTileLine, LinkedList<String> extensionChainList) { if (extensionChainList.contains(lookOutsideTileLine.getExtendedBaseName())) { throw new IllegalArgumentException("There is a cross reference in extension chain of tiles"); } extensionChainList.add(lookOutsideTileLine.getExtendedBaseName()); TileLine baseTileLine = findBaseTileLineFor(lookOutsideTileLine); if (baseTileLine.isExtending()) { extendTile(tile, baseTileLine, extensionChainList); } else { tile.setValue(baseTileLine.value); } if (baseTileLine.children != null) { for (TileLine childLine : baseTileLine.children) { tile.getTiles().put(childLine.key, convertTile(childLine)); } } } private TileLine findBaseTileLineFor(TileLine lookOutsideTileLine) { if (lookOutsideTileLine.key.equals(lookOutsideTileLine.getExtendedBaseName())) { throw new IllegalArgumentException("Wrong tile extension. Cannot extend from itself"); } if (lookOutsideTileLine.parent != null && lookOutsideTileLine.parent.children != null) { for (TileLine tileLine : lookOutsideTileLine.parent.children) { if (tileLine.key.equals(lookOutsideTileLine.getExtendedBaseName())) { return tileLine; } } } throw new IllegalArgumentException("Cannot extend from tile '" + lookOutsideTileLine.getExtendedBaseName() + "'. Such tile is not defined"); } private TileLine readAllTileLines(File file) throws IOException { LineIterator it = FileUtils.lineIterator(file, "UTF-8"); /** * Setting a root tile which will be a container for all tiles */ TileLine rootTileLine = new TileLine(); rootTileLine.indentation = -1; TileLine currentTileLine = rootTileLine; try { while (it.hasNext()) { String line = it.nextLine(); TileLine tileLine = readKeyValue(currentTileLine, line); if (tileLine != null) { currentTileLine = tileLine; } } } finally { LineIterator.closeQuietly(it); } return rootTileLine; } private TileLine readKeyValue(TileLine previousTile, String line) throws IOException { LineBuilder lineBuilder = new LineBuilder().processLine(line); if (!lineBuilder.isAWhiteSpace()) { TileLine tileLine = lineBuilder.toTileLine(); findParentForAttaching(previousTile, tileLine.indentation).attach(tileLine); return tileLine; } return null; } /* * Find a tile-line to which the current tile-line can be attached */ private TileLine findParentForAttaching(TileLine lookupTile, int indentation) { if (indentation > lookupTile.indentation) { return lookupTile; } else if (indentation == lookupTile.indentation) { return lookupTile.parent; } else { return findParentForAttaching(lookupTile.parent, indentation); } } private class LineBuilder { private static final char SPLITTER = ':'; private static final char COMMENT = '#'; private static final char SPACE = ' '; private static final int INDENTATION = 0; private static final int KEY = 1; private static final int VALUE = 2; private int indentation = 0; private StringBuilder key = new StringBuilder(""); private StringBuilder value = new StringBuilder(""); public boolean isAWhiteSpace() { return key.toString().isEmpty(); } public TileLine toTileLine() { TileLine tileLine = new TileLine(); tileLine.indentation = indentation; tileLine.key = key.toString().trim(); tileLine.value = value.toString().trim(); return tileLine; } /* * Used for identifying at which token the builder is working currently * * 0 - indentation * 1 - key * 2 - value */ private int state = INDENTATION; public LineBuilder processLine(String line) throws IOException { Reader reader = new StringReader(line); int r; while ((r = reader.read()) != -1) { char ch = (char) r; if (ch == COMMENT) { return this; } switch (state) { case INDENTATION: if (ch == SPACE && state == INDENTATION) { indentation++; } else { state = KEY; key.append(ch); } break; case KEY: if (ch != SPLITTER) { key.append(ch); } else { state = VALUE; } break; case VALUE: value.append(ch); break; } ; } return this; } } public Tile findTile(String name) { if (tiles != null) { return tiles.get(name); } return null; } public Map<String, Tile> getTiles() { return tiles; } private class TileLine { private TileLine parent; private String key; private String value; private int indentation; private List<TileLine> children; public boolean isExtending() { return value.startsWith("@"); } public String getExtendedBaseName() { return value.substring(1).trim(); } public void attach(TileLine child) { child.parent = this; if (children == null) { children = new LinkedList<TilesContainer.TileLine>(); } children.add(child); } } }