001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.named.support;
020
021import java.util.ArrayDeque;
022import java.util.Collections;
023import java.util.Deque;
024import java.util.Map;
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.concurrent.TimeUnit;
027
028import org.eclipse.aether.named.NamedLock;
029import org.eclipse.aether.named.NamedLockKey;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * Support class for {@link NamedLock} implementations providing reference counting.
035 */
036public abstract class NamedLockSupport implements NamedLock {
037    protected final Logger logger = LoggerFactory.getLogger(getClass());
038
039    private final NamedLockKey key;
040
041    private final NamedLockFactorySupport factory;
042
043    private final ConcurrentHashMap<Thread, Deque<String>> diagnosticState; // non-null only if diag enabled
044
045    public NamedLockSupport(final NamedLockKey key, final NamedLockFactorySupport factory) {
046        this.key = key;
047        this.factory = factory;
048        this.diagnosticState = factory.isDiagnosticEnabled() ? new ConcurrentHashMap<>() : null;
049    }
050
051    @Override
052    public NamedLockKey key() {
053        return key;
054    }
055
056    @Override
057    public boolean lockShared(long time, TimeUnit unit) throws InterruptedException {
058        Deque<String> steps = null;
059        if (diagnosticState != null) {
060            steps = diagnosticState.computeIfAbsent(Thread.currentThread(), k -> new ArrayDeque<>());
061        }
062        if (steps != null) {
063            steps.push("wait-shared");
064        }
065        boolean result = doLockShared(time, unit);
066        if (steps != null) {
067            steps.pop();
068            if (result) {
069                steps.push("shared");
070            }
071        }
072        return result;
073    }
074
075    protected abstract boolean doLockShared(long time, TimeUnit unit) throws InterruptedException;
076
077    @Override
078    public boolean lockExclusively(long time, TimeUnit unit) throws InterruptedException {
079        Deque<String> steps = null;
080        if (diagnosticState != null) {
081            steps = diagnosticState.computeIfAbsent(Thread.currentThread(), k -> new ArrayDeque<>());
082        }
083        if (steps != null) {
084            steps.push("wait-exclusive");
085        }
086        boolean result = doLockExclusively(time, unit);
087        if (steps != null) {
088            steps.pop();
089            if (result) {
090                steps.push("exclusive");
091            }
092        }
093        return result;
094    }
095
096    protected abstract boolean doLockExclusively(long time, TimeUnit unit) throws InterruptedException;
097
098    @Override
099    public void unlock() {
100        doUnlock();
101        if (diagnosticState != null) {
102            diagnosticState
103                    .computeIfAbsent(Thread.currentThread(), k -> new ArrayDeque<>())
104                    .pop();
105        }
106    }
107
108    protected abstract void doUnlock();
109
110    @Override
111    public final void close() {
112        doClose();
113    }
114
115    protected void doClose() {
116        factory.closeLock(key);
117    }
118
119    /**
120     * Returns the diagnostic state (if collected) or empty map, never {@code null}.
121     *
122     * @since 1.9.11
123     */
124    public Map<Thread, Deque<String>> diagnosticState() {
125        if (diagnosticState != null) {
126            return diagnosticState;
127        } else {
128            return Collections.emptyMap();
129        }
130    }
131
132    @Override
133    public String toString() {
134        return getClass().getSimpleName() + "{" + "key='" + key + '\'' + '}';
135    }
136}