/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* 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 com.intellij.formatting;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class WrapImpl extends Wrap {
/**
* The block where the wrap needs to happen if the CHOP wrap mode is used and the chain of blocks exceeds the right margin.
*/
private LeafBlockWrapper myChopStartBlock = null;
private int myWrapOffset = -1;
private int myFlags;
private static final Set<WrapImpl> emptyParentsSet = Collections.emptySet();
private Set<WrapImpl> myParents = emptyParentsSet;
private Map<WrapImpl, Collection<LeafBlockWrapper>> myIgnoredWraps;
private static final int IGNORE_PARENT_WRAPS_MASK = 1;
private static final int ACTIVE_MASK = 2;
private static final int WRAP_FIRST_ELEMENT_MASK = 4;
private static final int TYPE_MASK = 0x18;
private static final int TYPE_SHIFT = 3;
private static final Type[] myTypes = Type.values();
public boolean isChildOf(@Nullable final WrapImpl wrap, LeafBlockWrapper leaf) {
if (getIgnoreParentWraps()) return false;
if (leaf != null && myIgnoredWraps != null) {
Collection<LeafBlockWrapper> leaves = myIgnoredWraps.get(wrap);
if (leaves != null && leaves.contains(leaf)) {
return false;
}
}
for (WrapImpl parent : myParents) {
if (parent == wrap) return true;
if (parent.isChildOf(wrap, leaf)) return true;
}
return false;
}
/**
* Allows to register given wrap as a parent of the current wrap.
* <p/>
* {@code 'Parent'} wrap registration here means that {@link #isChildOf(WrapImpl, LeafBlockWrapper)} returns
* {@code 'true'} if given wrap is used as a {@code 'parent'} argument.
*
* @param parent parent wrap to register for the current wrap
*/
void registerParent(@Nullable WrapImpl parent) {
if (parent == this) return;
if (parent == null) return;
if (parent.isChildOf(this, null)) return;
if (myParents == emptyParentsSet) myParents = new HashSet<>(5);
myParents.add(parent);
}
/**
* Resets the following state of the current wrap object:
* <ul>
* <li>'{@link #getChopStartBlock() firstEntry}' property value is set to {@code null};</li>
* <li>'{@link #getWrapOffset() firstPosition}' property value is set to {@code '-1'};</li>
* <li>'{@link #isActive() isActive}' property value is set to {@code 'false'};</li>
* </ul>
*/
public void reset() {
myChopStartBlock = null;
myWrapOffset = -1;
myFlags &=~ ACTIVE_MASK;
}
/**
* Allows to check if single wrap is {@link #registerParent(WrapImpl) registered} for the current wrap and return
* it in case of success.
*
* @return single wrap registered as a parent of the current wrap if any;
* {@code null} if no wraps or more than one wrap is registered as a parent for the current wrap
*/
public WrapImpl getParent(){
if (myParents != null && myParents.size() == 1) {
return myParents.iterator().next();
}
return null;
}
public final boolean getIgnoreParentWraps() {
return (myFlags & IGNORE_PARENT_WRAPS_MASK) != 0;
}
/**
* Allows to mark given wrap as {@code 'ignored'} for the given block. I.e. 'false' will be returned
* for subsequent calls to {@link #isChildOf(WrapImpl, LeafBlockWrapper)} with the same arguments.
*
* @param wrap target wrap
* @param currentBlock target block for which given wrap should be ignored
*/
public void ignoreParentWrap(@Nullable final WrapImpl wrap, final LeafBlockWrapper currentBlock) {
if (myIgnoredWraps == null) {
myIgnoredWraps = new HashMap<>(5);
}
myIgnoredWraps.putIfAbsent(wrap, new HashSet<>(2));
myIgnoredWraps.get(wrap).add(currentBlock);
}
public enum Type{
DO_NOT_WRAP, WRAP_AS_NEEDED, CHOP_IF_NEEDED, WRAP_ALWAYS
}
public LeafBlockWrapper getChopStartBlock() {
return myChopStartBlock;
}
/**
* Performs the following changes at wrap object state:
* <ul>
* <li>'{@link #getChopStartBlock() firstEntry}' property value is dropped (set to {@code null})</li>
* <li>'{@link #isActive() isActive}' property value is set (to {@code true})</li>
* </ul>
*/
public void setActive() {
myChopStartBlock = null;
myFlags |= ACTIVE_MASK;
}
/**
* Applies given value to the '{@link #getWrapOffset() firstPosition}' property value if it's value is undefined at the moment
* (has negative value).
*
* @param startOffset new '{@link #getWrapOffset() firstPosition}' property value to use if current value is undefined (negative)
*/
public void setWrapOffset(final int startOffset) {
if (myWrapOffset < 0) {
myWrapOffset = startOffset;
}
}
/**
* @return '{@link #getWrapOffset() firstPosition}' property value defined previously via {@link #setWrapOffset(int)} if any;
* {@code '-1'} otherwise
*/
public int getWrapOffset() {
return myWrapOffset;
}
public WrapImpl(WrapType type, boolean wrapFirstElement) {
Type myType;
switch(type) {
case NORMAL: myType = Type.WRAP_AS_NEEDED;break;
case NONE: myType= Type.DO_NOT_WRAP;break;
case ALWAYS: myType = Type.WRAP_ALWAYS; break;
case CHOP_DOWN_IF_LONG:
default: myType = Type.CHOP_IF_NEEDED;
}
myFlags |= (wrapFirstElement ? WRAP_FIRST_ELEMENT_MASK:0) | (myType.ordinal() << TYPE_SHIFT);
}
public final Type getType() {
return myTypes[(myFlags & TYPE_MASK) >>> TYPE_SHIFT];
}
/**
* Allows to check if current wrap object is configured to wrap first element. This property is defined at
* {@link #WrapImpl(WrapType, boolean) constructor} during object initialization and can't be changed later.
*
* @return {@code 'wrapFirstElement'} property value
*/
public final boolean isWrapFirstElement() {
return (myFlags & WRAP_FIRST_ELEMENT_MASK) != 0;
}
public void saveChopBlock(LeafBlockWrapper current) {
if (myChopStartBlock == null) {
myChopStartBlock = current;
}
}
public final boolean isActive() {
return (myFlags & ACTIVE_MASK) != 0;
}
public String toString() {
return getType().toString();
}
/**
* Allows to instruct current wrap to ignore all parent wraps, i.e. all calls to {@link #isChildOf(WrapImpl, LeafBlockWrapper)}
* return {@code 'false'} after invocation of this method.
*/
@Override
public void ignoreParentWraps() {
myFlags |= IGNORE_PARENT_WRAPS_MASK;
}
}