All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tinylog.path.DynamicPath Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2018 Martin Winandy
 *
 * 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.tinylog.path;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;

import org.tinylog.runtime.RuntimeProvider;
import org.tinylog.runtime.Timestamp;

/**
 * A dynamic path represents a path with patterns that can be resolved as a real path to a file at runtime.
 */
public final class DynamicPath {

	private static final String DEFAULT_DATE_FORMAT_PATTERN = "yyyy-MM-dd_HH-mm-ss";
	private static final String DEFAULT_LOG_FILENAME = "log";

	private final List segments;
	private final File folder;
	private final List plainTexts;
	private final String suffix;

	/**
	 * @param path
	 *            Path with patterns
	 * @throws IllegalArgumentException
	 *             Path contains invalid or incomplete patterns
	 */
	public DynamicPath(final String path) {
		String normalizedPath = path.replace('/', File.separatorChar);

		segments = new ArrayList();
		plainTexts = new ArrayList();

		String prefix = "";
		int start = 0;
		boolean token = false;

		for (int i = 0; i < normalizedPath.length(); ++i) {
			char character = normalizedPath.charAt(i);

			if (character == '{') {
				if (token) {
					throw new IllegalArgumentException("Closing curly bracket is missing: '" + path + "'");
				} else if (i > 0 && normalizedPath.charAt(i - 1) == '}') {
					throw new IllegalArgumentException("Two patterns must be separated by at least one character: '" + path + "'");
				} else {
					if (i > start) {
						if (prefix.isEmpty() && segments.isEmpty()) {
							prefix = normalizedPath.substring(0, i);
						}

						String text = normalizedPath.substring(start, i);
						segments.add(new PlainTextSegment(text));
						plainTexts.add(text);
					}

					start = i + 1;
					token = true;
				}
			} else if (character == '}') {
				if (token) {
					segments.add(parseSegment(normalizedPath, normalizedPath.substring(start, i)));
					start = i + 1;
					token = false;
				} else {
					throw new IllegalArgumentException("Opening curly bracket is missing: '" + path + "'");
				}
			}
		}

		if (token) {
			throw new IllegalArgumentException("Closing curly bracket is missing: '" + path + "'");
		} else if (start < normalizedPath.length() - 1) {
			if (prefix.isEmpty() && segments.isEmpty()) {
				prefix = normalizedPath;
			}

			String text = normalizedPath.substring(start);
			int separator = Math.max(text.lastIndexOf(File.separatorChar), text.lastIndexOf('/'));
			segments.add(new PlainTextSegment(text));
			plainTexts.add(text);
			suffix = separator == -1 ? text : text.substring(separator + 1);
		} else {
			suffix = "";
		}

		int separator = Math.max(prefix.lastIndexOf(File.separatorChar), prefix.lastIndexOf('/'));
		folder = new File(separator == -1 ? "" : prefix.substring(0, separator)).getAbsoluteFile();
	}

	/**
	 * Resolves all patterns and generates a real path for a (new) file.
	 *
	 * @return Generated path
	 */
	public String resolve() {
		Timestamp timestamp = RuntimeProvider.createTimestamp();
		StringBuilder builder = new StringBuilder();
		for (Segment segment : segments) {
			builder.append(segment.createToken(builder.toString(), timestamp));
		}
		return builder.toString();
	}

	/**
	 * Gets all original files and backup files that are compatible with the dynamic path. The returned file tuples are
	 * sorted by the last modification date. The most recently modified file tuples are at the top, the oldest at the
	 * bottom of the list.
	 *
	 * @param backupSuffix
	 *            Optional file extension for backup files (can be {@code null} if there are no separate backup files)
	 * @return Found file tuples
	 */
	public List getAllFiles(final String backupSuffix) {
		List tuples = new ArrayList();

		Collection originals = backupSuffix == null ? new ArrayList() : new HashSet();
		collectFiles(folder, suffix, originals);

		if (backupSuffix != null) {
			Collection backups = new ArrayList();
			collectFiles(folder, suffix + backupSuffix, backups);

			for (File backup : backups) {
				String path = backup.getAbsolutePath();
				File original = new File(path.substring(0, path.length() - backupSuffix.length()));
				tuples.add(new FileTuple(original, backup));
				originals.remove(original);
			}
		}

		for (File original : originals) {
			String path = original.getAbsolutePath();
			File backup = new File(backupSuffix == null ? path : path + backupSuffix);
			tuples.add(new FileTuple(original, backup));
		}

		Collections.sort(tuples, LastModifiedFileTupleComparator.INSTANCE);
		return tuples;
	}

	/**
	 * Checks if an already existing file is compatible with this dynamic path.
	 *
	 * @param file
	 *            File to check
	 * @return {@code true} if passed file is compatible, {@code false} if not
	 */
	public boolean isValid(final File file) {
		return isValid(file.getAbsolutePath(), 0, 0);
	}

	/**
	 * Collects files from a folder and all nested sub folders.
	 *
	 * @param folder
	 *            Base folder for starting search
	 * @param suffix
	 *            Expected suffix for file paths
	 * @param found
	 *            All found files will be added to this list
	 */
	private void collectFiles(final File folder, final String suffix, final Collection found) {
		File[] files = folder.listFiles();
		if (files != null) {
			for (File file : files) {
				if (file.isDirectory()) {
					collectFiles(file, suffix, found);
				} else if (file.isFile() && file.getAbsolutePath().endsWith(suffix)) {
					int index = 0;

					for (String text : plainTexts) {
						index = file.getAbsolutePath().indexOf(text, index);
						if (index == -1) {
							break;
						} else {
							index += text.length();
						}
					}

					if (index >= 0) {
						found.add(file.getAbsoluteFile());
					}
				}
			}
		}
	}

	/**
	 * Checks if a partial path to a file is compatible with this dynamic path.
	 *
	 * @param path
	 *            Full path to file
	 * @param pathPosition
	 *            Start position for checking passed path
	 * @param segmentIndex
	 *            Index of segment that will be used for check
	 * @return {@code true} if passed path is compatible, {@code false} if not
	 */
	private boolean isValid(final String path, final int pathPosition, final int segmentIndex) {
		Segment segment = segments.get(segmentIndex);
		String expectedValue = segment.getStaticText();

		if (pathPosition == 0) {
			File file = new File(expectedValue == null ? "" : expectedValue).getAbsoluteFile();
			if (file.isDirectory()) {
				expectedValue = file.getAbsolutePath() + File.separator;
			} else {
				expectedValue = file.getAbsolutePath();
			}
		}

		if (expectedValue == null) {
			if (segmentIndex == segments.size() - 1) {
				return segment.validateToken(path.substring(pathPosition));
			} else {
				String nextValue = segments.get(segmentIndex + 1).getStaticText();
				for (int i = path.indexOf(nextValue, pathPosition); i >= 0; i = path.indexOf(nextValue, i + 1)) {
					if (segment.validateToken(path.substring(pathPosition, i)) && isValid(path, i, segmentIndex + 1)) {
						return true;
					}
				}
				return false;
			}
		} else if (path.startsWith(expectedValue, pathPosition)) {
			if (segmentIndex == segments.size() - 1) {
				return pathPosition + expectedValue.length() == path.length();
			} else {
				return isValid(path, pathPosition + expectedValue.length(), segmentIndex + 1);
			}
		} else {
			return false;
		}
	}

	/**
	 * Parses a token from a pattern as a segment.
	 *
	 * @param path
	 *            Full path with patterns
	 * @param token
	 *            Token from a pattern
	 * @return Created segment that represents the passed token
	 * @throws IllegalArgumentException
	 *             Token is unknown or contains an unexpected parameter
	 */
	private static Segment parseSegment(final String path, final String token) {
		int separator = token.indexOf(':');

		String name;
		String parameter;

		if (separator == -1) {
			name = token.trim();
			parameter = null;
		} else {
			name = token.substring(0, separator).trim();
			parameter = token.substring(separator + 1).trim();
		}

		if ("date".equals(name)) {
			return new DateSegment(parameter == null ? DEFAULT_DATE_FORMAT_PATTERN : parameter);
		} else if ("count".equals(name) && parameter == null) {
			return new CountSegment();
		} else if ("pid".equals(name) && parameter == null) {
			return new ProcessIdSegment();
		} else if ("dynamic".equals(name)) {
			return new DynamicSegment(parameter == null ? DEFAULT_LOG_FILENAME : parameter);
		} else {
			throw new IllegalArgumentException("Invalid token '" + token + "' in '" + path + "'");
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy