    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements. See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache license, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License. You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the license for the specific language governing permissions and
    015     * limitations under the license.
    016     */
    017    package org.apache.logging.log4j;
    018
    019    import java.util.Arrays;
    020    import java.util.concurrent.ConcurrentHashMap;
    021    import java.util.concurrent.ConcurrentMap;
    022
    023
    024    /**
    025     * Applications create Markers by using the Marker Manager. All Markers created by this Manager are
    026     * immutable.
    027     */
    028    public final class MarkerManager {
    029
    030        private static final ConcurrentMap<String, Marker> MARKERS = new ConcurrentHashMap<String, Marker>();
    031
    032        private MarkerManager() {
    033            // do nothing
    034        }
    035
    036        /**
    037         * Clears all markers.
    038         */
    039        public static void clear() {
    040            MARKERS.clear();
    041        }
    042
    043        /**
    044         * Retrieve a Marker or create a Marker that has no parent.
    045         * @param name The name of the Marker.
    046         * @return The Marker with the specified name.
    047         * @throws IllegalArgumentException if the argument is {@code null}
    048         */
    049        public static Marker getMarker(final String name) {
    050            MARKERS.putIfAbsent(name, new Log4jMarker(name));
    051            return MARKERS.get(name);
    052        }
    053
    054        /**
    055         * Retrieves or creates a Marker with the specified parent. The parent must have been previously created.
    056         * @param name The name of the Marker.
    057         * @param parent The name of the parent Marker.
    058         * @return The Marker with the specified name.
    059         * @throws IllegalArgumentException if the parent Marker does not exist.
    060         * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
    061         */
    062        @Deprecated
    063        public static Marker getMarker(final String name, final String parent) {
    064            final Marker parentMarker = MARKERS.get(parent);
    065            if (parentMarker == null) {
    066                throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined");
    067            }
    068            @SuppressWarnings("deprecation")
    069            final Marker marker = getMarker(name, parentMarker);
    070            return marker;
    071        }
    072
    073        /**
    074         * Retrieves or creates a Marker with the specified parent.
    075         * @param name The name of the Marker.
    076         * @param parent The parent Marker.
    077         * @return The Marker with the specified name.
    078         * @throws IllegalArgumentException if any argument is {@code null}
    079         * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
    080         */
    081        @Deprecated
    082        public static Marker getMarker(final String name, final Marker parent) {
    083            MARKERS.putIfAbsent(name, new Log4jMarker(name));
    084            return MARKERS.get(name).addParents(parent);
    085        }
    086
    087        /**
    088         * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em>
    089         * <p>
    090         * The actual Marker implementation.
    091         * </p>
    092         * <p>
    093         * <em>Internal note: We could make this class package private instead of public if the class
    094         * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn}
    095         * is moved to this package and would of course stay in its current module.</em>
    096         * </p>
    097         */
    098        public static class Log4jMarker implements Marker {
    099
    100            private static final long serialVersionUID = 100L;
    101
    102            private final String name;
    103
    104            private volatile Marker[] parents;
    105
    106            /**
    107             * Required by JAXB and Jackson for XML and JSON IO.
    108             */
    109            @SuppressWarnings("unused")
    110            private Log4jMarker() {
    111                this.name = null;
    112                this.parents = null;
    113            }
    114
    115            /**
    116             * Constructs a new Marker.
    117             * @param name the name of the Marker.
    118             * @throws IllegalArgumentException if the argument is {@code null}
    119             */
    120            public Log4jMarker(final String name) {
    121                if (name == null) {
    122                    // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker
    123                    // name seems rather pointless. To get an "anonymous" Marker, just use an empty string.
    124                    throw new IllegalArgumentException("Marker name cannot be null.");
    125                }
    126                this.name = name;
    127                this.parents = null;
    128            }
    129
    130            // TODO: use java.util.concurrent
    131
    132            @Override
    133            public synchronized Marker addParents(final Marker... parents) {
    134                if (parents == null) {
    135                    throw new IllegalArgumentException("A parent marker must be specified");
    136                }
    137                // It is not strictly necessary to copy the variable here but it should perform better than
    138                // Accessing a volatile variable multiple times.
    139                final Marker[] localParents = this.parents;
    140                // Don't add a parent that is already in the hierarchy.
    141                int count = 0;
    142                int size = parents.length;
    143                if (localParents != null) {
    144                    for (final Marker parent : parents) {
    145                        if (!(contains(parent, localParents) || parent.isInstanceOf(this))) {
    146                            ++count;
    147                        }
    148                    }
    149                    if (count == 0) {
    150                        return this;
    151                    }
    152                    size = localParents.length + count;
    153                }
    154                final Marker[] markers = new Marker[size];
    155                if (localParents != null) {
    156                    // It's perfectly OK to call arraycopy in a synchronized context; it's still faster
    157                    //noinspection CallToNativeMethodWhileLocked
    158                    System.arraycopy(localParents, 0, markers, 0, localParents.length);
    159                }
    160                int index = localParents == null ? 0 : localParents.length;
    161                for (final Marker parent : parents) {
    162                    if (localParents == null || !(contains(parent, localParents) || parent.isInstanceOf(this))) {
    163                        markers[index++] = parent;
    164                    }
    165                }
    166                this.parents = markers;
    167                return this;
    168            }
    169
    170            @Override
    171            public synchronized boolean remove(final Marker parent) {
    172                if (parent == null) {
    173                    throw new IllegalArgumentException("A parent marker must be specified");
    174                }
    175                final Marker[] localParents = this.parents;
    176                if (localParents == null) {
    177                    return false;
    178                }
    179                final int localParentsLength = localParents.length;
    180                if (localParentsLength == 1) {
    181                    if (localParents[0].equals(parent)) {
    182                        parents = null;
    183                        return true;
    184                    }
    185                    return false;
    186                }
    187                int index = 0;
    188                final Marker[] markers = new Marker[localParentsLength - 1];
    189                //noinspection ForLoopReplaceableByForEach
    190                for (int i = 0; i < localParentsLength; i++) {
    191                    final Marker marker = localParents[i];
    192                    if (!marker.equals(parent)) {
    193                        if (index == localParentsLength - 1) {
    194                            // no need to swap array
    195                            return false;
    196                        }
    197                        markers[index++] = marker;
    198                    }
    199                }
    200                parents = markers;
    201                return true;
    202            }
    203
    204            @Override
    205            public Marker setParents(final Marker... markers) {
    206                if (markers == null || markers.length == 0) {
    207                    this.parents = null;
    208                } else {
    209                    final Marker[] array = new Marker[markers.length];
    210                    System.arraycopy(markers, 0, array, 0, markers.length);
    211                    this.parents = array;
    212                }
    213                return this;
    214            }
    215
    216            @Override
    217            public String getName() {
    218                return this.name;
    219            }
    220
    221            @Override
    222            public Marker[] getParents() {
    223                if (this.parents == null) {
    224                    return null;
    225                }
    226                return Arrays.copyOf(this.parents, this.parents.length);
    227            }
    228
    229            @Override
    230            public boolean hasParents() {
    231                return this.parents != null;
    232            }
    233
    234            @Override
    235            public boolean isInstanceOf(final Marker marker) {
    236                if (marker == null) {
    237                    throw new IllegalArgumentException("A marker parameter is required");
    238                }
    239                if (this == marker) {
    240                    return true;
    241                }
    242                final Marker[] localParents = parents;
    243                if (localParents != null) {
    244                    // With only one or two parents the for loop is slower.
    245                    final int localParentsLength = localParents.length;
    246                    if (localParentsLength == 1) {
    247                        return checkParent(localParents[0], marker);
    248                    }
    249                    if (localParentsLength == 2) {
    250                        return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
    251                    }
    252                    //noinspection ForLoopReplaceableByForEach
    253                    for (int i = 0; i < localParentsLength; i++) {
    254                        final Marker localParent = localParents[i];
    255                        if (checkParent(localParent, marker)) {
    256                            return true;
    257                        }
    258                    }
    259                }
    260                return false;
    261            }
    262
    263            @Override
    264            public boolean isInstanceOf(final String markerName) {
    265                if (markerName == null) {
    266                    throw new IllegalArgumentException("A marker name is required");
    267                }
    268                if (markerName.equals(this.getName())) {
    269                    return true;
    270                }
    271                // Use a real marker for child comparisons. It is faster than comparing the names.
    272                final Marker marker = MARKERS.get(markerName);
    273                if (marker == null) {
    274                    return false;
    275                }
    276                final Marker[] localParents = parents;
    277                if (localParents != null) {
    278                    final int localParentsLength = localParents.length;
    279                    if (localParentsLength == 1) {
    280                        return checkParent(localParents[0], marker);
    281                    }
    282                    if (localParentsLength == 2) {
    283                        return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
    284                    }
    285                    //noinspection ForLoopReplaceableByForEach
    286                    for (int i = 0; i < localParentsLength; i++) {
    287                        final Marker localParent = localParents[i];
    288                        if (checkParent(localParent, marker)) {
    289                            return true;
    290                        }
    291                    }
    292                }
    293
    294                return false;
    295            }
    296
    297            private static boolean checkParent(final Marker parent, final Marker marker) {
    298                if (parent == marker) {
    299                    return true;
    300                }
    301                final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker)parent).parents : parent.getParents();
    302                if (localParents != null) {
    303                    final int localParentsLength = localParents.length;
    304                    if (localParentsLength == 1) {
    305                        return checkParent(localParents[0], marker);
    306                    }
    307                    if (localParentsLength == 2) {
    308                        return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
    309                    }
    310                    //noinspection ForLoopReplaceableByForEach
    311                    for (int i = 0; i < localParentsLength; i++) {
    312                        final Marker localParent = localParents[i];
    313                        if (checkParent(localParent, marker)) {
    314                            return true;
    315                        }
    316                    }
    317                }
    318                return false;
    319            }
    320
    321            /*
    322             * Called from add while synchronized.
    323             */
    324            private static boolean contains(final Marker parent, final Marker... localParents) {
    325                //noinspection ForLoopReplaceableByForEach
    326                // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms
    327                for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) {
    328                    final Marker marker = localParents[i];
    329                    if (marker == parent) {
    330                        return true;
    331                    }
    332                }
    333                return false;
    334            }
    335
    336            @Override
    337            public boolean equals(final Object o) {
    338                if (this == o) {
    339                    return true;
    340                }
    341                if (o == null || !(o instanceof Marker)) {
    342                    return false;
    343                }
    344                final Marker marker = (Marker) o;
    345                return name.equals(marker.getName());
    346            }
    347
    348            @Override
    349            public int hashCode() {
    350                return name.hashCode();
    351            }
    352
    353            @Override
    354            public String toString() {
    355                // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16)
    356                final StringBuilder sb = new StringBuilder(name);
    357                final Marker[] localParents = parents;
    358                if (localParents != null) {
    359                    addParentInfo(sb, localParents);
    360                }
    361                return sb.toString();
    362            }
    363
    364            private static void addParentInfo(final StringBuilder sb, final Marker... parents) {
    365                sb.append("[ ");
    366                boolean first = true;
    367                //noinspection ForLoopReplaceableByForEach
    368                for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) {
    369                    final Marker marker = parents[i];
    370                    if (!first) {
    371                        sb.append(", ");
    372                    }
    373                    first = false;
    374                    sb.append(marker.getName());
    375                    final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents();
    376                    if (p != null) {
    377                        addParentInfo(sb, p);
    378                    }
    379                }
    380                sb.append(" ]");
    381            }
    382        }
    383    }