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

com.linecorp.centraldogma.internal.Util Maven / Gradle / Ivy

Go to download

Highly-available version-controlled service configuration repository based on Git, ZooKeeper and HTTP/2 (centraldogma-common)

There is a newer version: 0.70.0
Show newest version
/*
 * Copyright 2017 LINE Corporation
 *
 * LINE Corporation licenses this file to you 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:
 *
 *   https://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 com.linecorp.centraldogma.internal;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.jayway.jsonpath.JsonPath;

/**
 * This class borrowed some of its methods from a NetUtil class which was part of Netty project.
 */
public final class Util {

    private static final Pattern FILE_NAME_PATTERN = Pattern.compile(
            "^(?:[-_0-9a-zA-Z](?:[-_.0-9a-zA-Z]*[-_0-9a-zA-Z])?)+$");
    private static final Pattern FILE_PATH_PATTERN = Pattern.compile(
            "^(?:/[-_0-9a-zA-Z](?:[-_.0-9a-zA-Z]*[-_0-9a-zA-Z])?)+$");
    private static final Pattern JSON_FILE_PATH_PATTERN = Pattern.compile(
            "^(?:/[-_0-9a-zA-Z](?:[-_.0-9a-zA-Z]*[-_0-9a-zA-Z])?)+\\.(?i)json$");
    private static final Pattern DIR_PATH_PATTERN = Pattern.compile(
            "^(?:/[-_0-9a-zA-Z](?:[-_.0-9a-zA-Z]*[-_0-9a-zA-Z])?)*/?$");
    private static final Pattern PATH_PATTERN_PATTERN = Pattern.compile("^[- /*_.,0-9a-zA-Z]+$");
    private static final Pattern EMAIL_PATTERN = Pattern.compile(
            "^[_A-Za-z0-9-+]+(?:\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(?:\\.[A-Za-z0-9]+)*(?:\\.[A-Za-z]{2,})$");
    private static final Pattern GENERAL_EMAIL_PATTERN = Pattern.compile(
            "^[_A-Za-z0-9-+]+(?:\\.[_A-Za-z0-9-]+)*@(.+)$");

    /**
     * Start with an alphanumeric character.
     * An alphanumeric character, minus, plus, underscore and dot are allowed in the middle.
     * End with an alphanumeric character.
     * Use this pattern for project and repository names that are entered by users.
     */
    public static final Pattern USER_INPUT_PROJECT_AND_REPO_NAME_PATTERN =
            Pattern.compile("^(?!.*\\.git$)[0-9A-Za-z](?:[-+_0-9A-Za-z.]*[0-9A-Za-z])?$");

    public static final String INTERNAL_PROJECT_PREFIX = "@";

    /**
     * The difference between this and {@link #USER_INPUT_PROJECT_AND_REPO_NAME_PATTERN} is that this
     * allows {@value INTERNAL_PROJECT_PREFIX} at the beginning for internal projects.
     */
    public static final Pattern PROJECT_AND_REPO_NAME_PATTERN =
            Pattern.compile("^(?!.*\\.git$)(" + INTERNAL_PROJECT_PREFIX +
                            "|[0-9A-Za-z])(?:[-+_0-9A-Za-z.]*[0-9A-Za-z])?$");

    public static String validateFileName(String name, String paramName) {
        requireNonNull(name, paramName);
        checkArgument(isValidFileName(name),
                      "%s: %s (expected: %s)", paramName, name, FILE_NAME_PATTERN);
        return name;
    }

    public static boolean isValidFileName(String name) {
        requireNonNull(name, "name");
        return !name.isEmpty() && FILE_NAME_PATTERN.matcher(name).matches();
    }

    public static String validateFilePath(String path, String paramName) {
        requireNonNull(path, paramName);
        checkArgument(isValidFilePath(path),
                      "%s: %s (expected: %s)", paramName, path, FILE_PATH_PATTERN);
        return path;
    }

    public static boolean isValidFilePath(String path) {
        requireNonNull(path, "path");
        return !path.isEmpty() && path.charAt(0) == '/' &&
               FILE_PATH_PATTERN.matcher(path).matches();
    }

    public static String validateJsonFilePath(String path, String paramName) {
        requireNonNull(path, paramName);
        checkArgument(isValidJsonFilePath(path),
                      "%s: %s (expected: %s)", paramName, path, JSON_FILE_PATH_PATTERN);
        return path;
    }

    public static boolean isValidJsonFilePath(String path) {
        requireNonNull(path, "path");
        return !path.isEmpty() && path.charAt(0) == '/' &&
               JSON_FILE_PATH_PATTERN.matcher(path).matches();
    }

    public static String validateJsonPath(String jsonPath, String paramName) {
        requireNonNull(jsonPath, paramName);
        checkArgument(isValidJsonPath(jsonPath),
                      "%s: %s (expected: a valid JSON path)", paramName, jsonPath);
        return jsonPath;
    }

    public static boolean isValidJsonPath(String jsonPath) {
        try {
            JsonPath.compile(jsonPath);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public static String validateDirPath(String path, String paramName) {
        requireNonNull(path, paramName);
        checkArgument(isValidDirPath(path),
                      "%s: %s (expected: %s)", paramName, path, DIR_PATH_PATTERN);
        return path;
    }

    public static boolean isValidDirPath(String path) {
        return isValidDirPath(path, false);
    }

    public static boolean isValidDirPath(String path, boolean mustEndWithSlash) {
        requireNonNull(path);
        if (mustEndWithSlash && !path.endsWith("/")) {
            return false;
        }
        return !path.isEmpty() && path.charAt(0) == '/' &&
               DIR_PATH_PATTERN.matcher(path).matches();
    }

    public static String validatePathPattern(String pathPattern, String paramName) {
        requireNonNull(pathPattern, paramName);
        checkArgument(isValidPathPattern(pathPattern),
                      "%s: %s (expected: %s)", paramName, pathPattern, PATH_PATTERN_PATTERN);
        return pathPattern;
    }

    public static boolean isValidPathPattern(String pathPattern) {
        requireNonNull(pathPattern, "pathPattern");
        return PATH_PATTERN_PATTERN.matcher(pathPattern).matches();
    }

    public static String validateProjectName(String projectName, String paramName) {
        requireNonNull(projectName, paramName);
        checkArgument(isValidProjectName(projectName),
                      "%s: %s (expected: %s)", paramName, projectName,
                      USER_INPUT_PROJECT_AND_REPO_NAME_PATTERN);
        return projectName;
    }

    public static boolean isValidProjectName(String projectName) {
        requireNonNull(projectName, "projectName");
        return USER_INPUT_PROJECT_AND_REPO_NAME_PATTERN.matcher(projectName).matches();
    }

    public static String validateRepositoryName(String repoName, String paramName) {
        requireNonNull(repoName, paramName);
        checkArgument(isValidRepositoryName(repoName),
                      "%s: %s (expected: %s)", paramName, repoName, USER_INPUT_PROJECT_AND_REPO_NAME_PATTERN);
        return repoName;
    }

    public static boolean isValidRepositoryName(String repoName) {
        requireNonNull(repoName, "repoName");
        return USER_INPUT_PROJECT_AND_REPO_NAME_PATTERN.matcher(repoName).matches();
    }

    public static String validateEmailAddress(String emailAddr, String paramName) {
        requireNonNull(emailAddr, paramName);
        checkArgument(isValidEmailAddress(emailAddr),
                      "%s: %s (expected: a valid e-mail address)", paramName, emailAddr);
        return emailAddr;
    }

    public static boolean isValidEmailAddress(String emailAddr) {
        requireNonNull(emailAddr, "emailAddr");
        if (EMAIL_PATTERN.matcher(emailAddr).matches()) {
            return true;
        }
        // Try to check whether the domain part is IP address format.
        final Matcher m = GENERAL_EMAIL_PATTERN.matcher(emailAddr);
        if (m.matches()) {
            final String domainPart = m.group(1);
            return isValidIpV4Address(domainPart) ||
                   isValidIpV6Address(domainPart);
        }
        return false;
    }

    public static String toEmailAddress(String emailAddr, String paramName) {
        requireNonNull(emailAddr, paramName);
        if (isValidEmailAddress(emailAddr)) {
            return emailAddr;
        }
        return emailAddr + "@localhost.localdomain";
    }

    public static String emailToUsername(String emailAddr, String paramName) {
        validateEmailAddress(emailAddr, paramName);
        return emailAddr.substring(0, emailAddr.indexOf('@'));
    }

    public static List stringToLines(String str) {
        final BufferedReader reader = new BufferedReader(new StringReader(str));
        final List lines = new ArrayList<>(128);
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                lines.add(line);
            }
        } catch (IOException ignored) {
            // Should never happen.
        }
        return lines;
    }

    /**
     * Returns the simplified name of the type of the specified object.
     */
    public static String simpleTypeName(Object obj) {
        if (obj == null) {
            return "null";
        }

        return simpleTypeName(obj.getClass(), false);
    }

    /**
     * Returns the simplified name of the specified type.
     */
    public static String simpleTypeName(Class clazz) {
        return simpleTypeName(clazz, false);
    }

    /**
     * Returns the simplified and (optionally) decapitalized name of the specified type.
     */
    public static String simpleTypeName(Class clazz, boolean decapitalize) {
        if (clazz == null) {
            return "null";
        }

        String className = clazz.getName();
        final int lastDotIdx = className.lastIndexOf('.');
        if (lastDotIdx >= 0) {
            className = className.substring(lastDotIdx + 1);
        }

        if (!decapitalize) {
            return className;
        }

        final StringBuilder buf = new StringBuilder(className.length());
        boolean lowercase = true;
        for (int i = 0; i < className.length(); i++) {
            final char c1 = className.charAt(i);
            final char c2;
            if (lowercase) {
                c2 = Character.toLowerCase(c1);
                if (c1 == c2) {
                    lowercase = false;
                }
            } else {
                c2 = c1;
            }
            buf.append(c2);
        }

        return buf.toString();
    }

    /**
     * Casts an object unsafely. Used when you want to suppress the unchecked type warnings.
     */
    @SuppressWarnings("unchecked")
    public static  T unsafeCast(Object o) {
        return (T) o;
    }

    /**
     * Makes sure the specified {@code values} and all its elements are not {@code null}.
     */
    public static  Iterable requireNonNullElements(Iterable values, String name) {
        requireNonNull(values, name);

        int i = 0;
        for (T v : values) {
            if (v == null) {
                throw new NullPointerException(name + '[' + i + ']');
            }
            i++;
        }

        return values;
    }

    private static boolean isValidIpV4Address(String ip) {
        return isValidIpV4Address(ip, 0, ip.length());
    }

    @SuppressWarnings("DuplicateBooleanBranch")
    private static boolean isValidIpV4Address(String ip, int from, int toExcluded) {
        final int len = toExcluded - from;
        int i;
        return len <= 15 && len >= 7 &&
               (i = ip.indexOf('.', from + 1)) > 0 && isValidIpV4Word(ip, from, i) &&
               (i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) &&
               (i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) &&
               isValidIpV4Word(ip, i + 1, toExcluded);
    }

    private static boolean isValidIpV4Word(CharSequence word, int from, int toExclusive) {
        final int len = toExclusive - from;
        final char c0;
        final char c1;
        final char c2;
        if (len < 1 || len > 3 || (c0 = word.charAt(from)) < '0') {
            return false;
        }
        if (len == 3) {
            return (c1 = word.charAt(from + 1)) >= '0' &&
                   (c2 = word.charAt(from + 2)) >= '0' &&
                   (c0 <= '1' && c1 <= '9' && c2 <= '9' ||
                    c0 == '2' && c1 <= '5' && (c2 <= '5' || c1 < '5' && c2 <= '9'));
        }
        return c0 <= '9' && (len == 1 || isValidNumericChar(word.charAt(from + 1)));
    }

    private static boolean isValidIpV6Address(String ip) {
        int end = ip.length();
        if (end < 2) {
            return false;
        }

        // strip "[]"
        int start;
        char c = ip.charAt(0);
        if (c == '[') {
            end--;
            if (ip.charAt(end) != ']') {
                // must have a close ]
                return false;
            }
            start = 1;
            c = ip.charAt(1);
        } else {
            start = 0;
        }

        int colons;
        int compressBegin;
        if (c == ':') {
            // an IPv6 address can start with "::" or with a number
            if (ip.charAt(start + 1) != ':') {
                return false;
            }
            colons = 2;
            compressBegin = start;
            start += 2;
        } else {
            colons = 0;
            compressBegin = -1;
        }

        int wordLen = 0;
        loop:
        for (int i = start; i < end; i++) {
            c = ip.charAt(i);
            if (isValidHexChar(c)) {
                if (wordLen < 4) {
                    wordLen++;
                    continue;
                }
                return false;
            }

            switch (c) {
                case ':':
                    if (colons > 7) {
                        return false;
                    }
                    if (ip.charAt(i - 1) == ':') {
                        if (compressBegin >= 0) {
                            return false;
                        }
                        compressBegin = i - 1;
                    } else {
                        wordLen = 0;
                    }
                    colons++;
                    break;
                case '.':
                    // case for the last 32-bits represented as IPv4 x:x:x:x:x:x:d.d.d.d

                    // check a normal case (6 single colons)
                    if (compressBegin < 0 && colons != 6 ||
                        // a special case ::1:2:3:4:5:d.d.d.d allows 7 colons with an
                        // IPv4 ending, otherwise 7 :'s is bad
                        colons == 7 && compressBegin >= start || colons > 7) {
                        return false;
                    }

                    // Verify this address is of the correct structure to contain an IPv4 address.
                    // It must be IPv4-Mapped or IPv4-Compatible
                    // (see https://tools.ietf.org/html/rfc4291#section-2.5.5).
                    final int ipv4Start = i - wordLen;
                    int j = ipv4Start - 2; // index of character before the previous ':'.
                    if (isValidIPv4MappedChar(ip.charAt(j))) {
                        if (!isValidIPv4MappedChar(ip.charAt(j - 1)) ||
                            !isValidIPv4MappedChar(ip.charAt(j - 2)) ||
                            !isValidIPv4MappedChar(ip.charAt(j - 3))) {
                            return false;
                        }
                        j -= 5;
                    }

                    for (; j >= start; --j) {
                        final char tmpChar = ip.charAt(j);
                        if (tmpChar != '0' && tmpChar != ':') {
                            return false;
                        }
                    }

                    // 7 - is minimum IPv4 address length
                    int ipv4End = ip.indexOf('%', ipv4Start + 7);
                    if (ipv4End < 0) {
                        ipv4End = end;
                    }
                    return isValidIpV4Address(ip, ipv4Start, ipv4End);
                case '%':
                    // strip the interface name/index after the percent sign
                    end = i;
                    break loop;
                default:
                    return false;
            }
        }

        // normal case without compression
        if (compressBegin < 0) {
            return colons == 7 && wordLen > 0;
        }

        return compressBegin + 2 == end ||
               // 8 colons is valid only if compression in start or end
               wordLen > 0 && (colons < 8 || compressBegin <= start);
    }

    private static boolean isValidNumericChar(char c) {
        return c >= '0' && c <= '9';
    }

    private static boolean isValidIPv4MappedChar(char c) {
        return c == 'f' || c == 'F';
    }

    private static boolean isValidHexChar(char c) {
        return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f';
    }

    /**
     * Deletes the specified {@code directory} recursively.
     */
    public static void deleteFileTree(File directory) throws IOException {
        if (directory.exists()) {
            Files.walkFileTree(directory.toPath(), DeletingFileVisitor.INSTANCE);
        }
    }

    private Util() {}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy