From 45fd65689542a09ff49cf98c52227dc44976d7f9 Mon Sep 17 00:00:00 2001 From: "monica.dionisio" Date: Mon, 2 Jan 2012 14:05:58 -0200 Subject: [PATCH] ProjectConfig sections refactoring Add a new class RefConfigSection to be superclass for some sections in project.config file. This is a preparation to include "Merge Strategy per branch" feature which will add another section to project.config file. Change-Id: I559aad8325358823471665b24186470a76aaf967 --- .../gerrit/common/data/AccessSection.java | 28 +--- .../gerrit/common/data/RefConfigSection.java | 48 +++++++ .../common/errors/InvalidNameException.java | 4 + .../client/admin/AccessSectionEditor.java | 11 +- .../gerrit/client/admin/PermissionEditor.java | 4 +- .../rpc/project/ChangeProjectAccess.java | 21 +-- .../rpc/project/ProjectAccessFactory.java | 3 +- .../gerrit/server/git/ProjectConfig.java | 6 +- .../gerrit/server/project/RefControl.java | 25 +++- .../gerrit/server/project/SectionMatcher.java | 2 +- .../server/project/SectionSortCache.java | 106 +-------------- .../server/util/MostSpecificComparator.java | 124 ++++++++++++++++++ 12 files changed, 221 insertions(+), 161 deletions(-) create mode 100644 gerrit-common/src/main/java/com/google/gerrit/common/data/RefConfigSection.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/util/MostSpecificComparator.java diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java index b19adfa5fe..04f0ecd82a 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java @@ -23,40 +23,18 @@ import java.util.List; import java.util.Set; /** Portion of a {@link Project} describing access rules. */ -public class AccessSection implements Comparable { +public class AccessSection extends RefConfigSection implements + Comparable { /** Special name given to the global capabilities; not a valid reference. */ public static final String GLOBAL_CAPABILITIES = "GLOBAL_CAPABILITIES"; - /** Pattern that matches all references in a project. */ - public static final String ALL = "refs/*"; - - /** Pattern that matches all branches in a project. */ - public static final String HEADS = "refs/heads/*"; - - /** Prefix that triggers a regular expression pattern. */ - public static final String REGEX_PREFIX = "^"; - - /** @return true if the name is likely to be a valid access section name. */ - public static boolean isAccessSection(String name) { - return name.startsWith("refs/") || name.startsWith("^refs/"); - } - - protected String name; protected List permissions; protected AccessSection() { } public AccessSection(String refPattern) { - setName(refPattern); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; + super(refPattern); } public List getPermissions() { diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/RefConfigSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/RefConfigSection.java new file mode 100644 index 0000000000..490378ee44 --- /dev/null +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/RefConfigSection.java @@ -0,0 +1,48 @@ +// Copyright (C) 2011 The Android Open Source Project +// +// 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.google.gerrit.common.data; + +public abstract class RefConfigSection { + /** Pattern that matches all references in a project. */ + public static final String ALL = "refs/*"; + + /** Pattern that matches all branches in a project. */ + public static final String HEADS = "refs/heads/*"; + + /** Prefix that triggers a regular expression pattern. */ + public static final String REGEX_PREFIX = "^"; + + /** @return true if the name is likely to be a valid reference section name. */ + public static boolean isValid(String name) { + return name.startsWith("refs/") || name.startsWith("^refs/"); + } + + protected String name; + + public RefConfigSection() { + } + + public RefConfigSection(String name) { + setName(name); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidNameException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidNameException.java index 5eb6e3f0b7..d975aef37e 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidNameException.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InvalidNameException.java @@ -23,4 +23,8 @@ public class InvalidNameException extends Exception { public InvalidNameException() { super(MESSAGE); } + + public InvalidNameException(String invalidName) { + super(MESSAGE + ": " + invalidName); + } } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java index f4257b5b81..72ac1a448b 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java @@ -19,6 +19,7 @@ import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.ApprovalType; import com.google.gerrit.common.data.Permission; import com.google.gerrit.common.data.ProjectAccess; +import com.google.gerrit.common.data.RefConfigSection; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; @@ -122,7 +123,8 @@ public class AccessSectionEditor extends Composite implements void onDeleteSection(ClickEvent event) { isDeleted = true; - if (name.isVisible() && AccessSection.isAccessSection(name.getValue())){ + if (name.isVisible() + && RefConfigSection.isValid(name.getValue())) { deletedName.setInnerText(Util.M.deletedReference(name.getValue())); } else { @@ -185,7 +187,7 @@ public class AccessSectionEditor extends Composite implements name.setEnabled(!readOnly); deleteSection.setVisible(!readOnly); - if (AccessSection.isAccessSection(value.getName())) { + if (RefConfigSection.isValid(value.getName())) { name.setVisible(true); name.setIgnoreEditorValue(false); sectionType.setInnerText(Util.C.sectionTypeReference()); @@ -225,8 +227,9 @@ public class AccessSectionEditor extends Composite implements perms.add(varName); } } - } else if (AccessSection.isAccessSection(value.getName())) { - for (ApprovalType t : Gerrit.getConfig().getApprovalTypes().getApprovalTypes()) { + } else if (RefConfigSection.isValid(value.getName())) { + for (ApprovalType t : Gerrit.getConfig().getApprovalTypes() + .getApprovalTypes()) { String varName = Permission.LABEL + t.getCategory().getLabelName(); if (value.getPermission(varName) == null) { perms.add(varName); diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java index a11cd5c8ee..d10afd124d 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java @@ -24,6 +24,7 @@ import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.data.Permission; import com.google.gerrit.common.data.PermissionRange; import com.google.gerrit.common.data.PermissionRule; +import com.google.gerrit.common.data.RefConfigSection; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; @@ -114,7 +115,8 @@ public class PermissionEditor extends Composite implements Editor, rules = ListEditor.of(new RuleEditorSource()); exclusiveGroup.setEnabled(!readOnly); - exclusiveGroup.setVisible(AccessSection.isAccessSection(section.getName())); + exclusiveGroup.setVisible(RefConfigSection + .isValid(section.getName())); if (readOnly) { addContainer.removeFromParent(); diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java index 44aea10e8a..777329bd55 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java @@ -38,7 +38,6 @@ import com.google.inject.assistedinject.Assisted; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Repository; import java.io.IOException; import java.util.ArrayList; @@ -117,28 +116,12 @@ class ChangeProjectAccess extends Handler { } replace(config, toDelete, section); - } else if (AccessSection.isAccessSection(name)) { + } else if (AccessSection.isValid(name)) { if (!projectControl.controlForRef(name).isOwner()) { continue; } - if (name.startsWith(AccessSection.REGEX_PREFIX)) { - if (!Repository.isValidRefName(RefControl.shortestExample(name))) { - throw new InvalidNameException(); - } - - } else if (name.equals(AccessSection.ALL)) { - // This is a special case we have to allow, it fails below. - - } else if (name.endsWith("/*")) { - String prefix = name.substring(0, name.length() - 2); - if (!Repository.isValidRefName(prefix)) { - throw new InvalidNameException(); - } - - } else if (!Repository.isValidRefName(name)) { - throw new InvalidNameException(); - } + RefControl.validateRefPattern(name); replace(config, toDelete, section); } diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java index 8fd4256a08..7ac4ec38c5 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java @@ -18,6 +18,7 @@ import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.Permission; import com.google.gerrit.common.data.PermissionRule; import com.google.gerrit.common.data.ProjectAccess; +import com.google.gerrit.common.data.RefConfigSection; import com.google.gerrit.common.errors.NoSuchGroupException; import com.google.gerrit.httpd.rpc.Handler; import com.google.gerrit.reviewdb.client.AccountGroup; @@ -122,7 +123,7 @@ class ProjectAccessFactory extends Handler { ownerOf.add(name); } - } else if (AccessSection.isAccessSection(name)) { + } else if (RefConfigSection.isValid(name)) { RefControl rc = pc.controlForRef(name); if (rc.isOwner()) { local.add(section); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java index 7f2ed00b13..d15095b271 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java @@ -14,7 +14,6 @@ package com.google.gerrit.server.git; -import static com.google.gerrit.common.data.AccessSection.isAccessSection; import static com.google.gerrit.common.data.Permission.isPermission; import com.google.gerrit.common.data.AccessSection; @@ -27,6 +26,7 @@ import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.Project.State; import com.google.gerrit.reviewdb.client.Project.SubmitType; import com.google.gerrit.server.account.GroupCache; +import com.google.gerrit.common.data.RefConfigSection; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.CommitBuilder; @@ -225,7 +225,7 @@ public class ProjectConfig extends VersionedMetaData { accessSections = new HashMap(); for (String refName : rc.getSubsections(ACCESS)) { - if (isAccessSection(refName)) { + if (RefConfigSection.isValid(refName)) { AccessSection as = getAccessSection(refName, true); for (String varName : rc.getStringList(ACCESS, refName, KEY_GROUP_PERMISSIONS)) { @@ -421,7 +421,7 @@ public class ProjectConfig extends VersionedMetaData { } for (String name : rc.getSubsections(ACCESS)) { - if (isAccessSection(name) && !accessSections.containsKey(name)) { + if (RefConfigSection.isValid(name) && !accessSections.containsKey(name)) { rc.unsetSection(ACCESS, name); } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java index 11f44e0500..a8656034c2 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java @@ -18,6 +18,8 @@ import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.Permission; import com.google.gerrit.common.data.PermissionRange; import com.google.gerrit.common.data.PermissionRule; +import com.google.gerrit.common.data.RefConfigSection; +import com.google.gerrit.common.errors.InvalidNameException; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; @@ -26,6 +28,7 @@ import com.google.gerrit.server.git.GitRepositoryManager; import dk.brics.automaton.RegExp; import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; @@ -400,7 +403,7 @@ public class RefControl { return mine; } - static boolean isRE(String refPattern) { + public static boolean isRE(String refPattern) { return refPattern.startsWith(AccessSection.REGEX_PREFIX); } @@ -414,10 +417,28 @@ public class RefControl { } } - static RegExp toRegExp(String refPattern) { + public static RegExp toRegExp(String refPattern) { if (isRE(refPattern)) { refPattern = refPattern.substring(1); } return new RegExp(refPattern, RegExp.NONE); } + + public static void validateRefPattern(String refPattern) + throws InvalidNameException { + if (refPattern.startsWith(RefConfigSection.REGEX_PREFIX)) { + if (!Repository.isValidRefName(RefControl.shortestExample(refPattern))) { + throw new InvalidNameException(refPattern); + } + } else if (refPattern.equals(RefConfigSection.ALL)) { + // This is a special case we have to allow, it fails below. + } else if (refPattern.endsWith("/*")) { + String prefix = refPattern.substring(0, refPattern.length() - 2); + if (!Repository.isValidRefName(prefix)) { + throw new InvalidNameException(refPattern); + } + } else if (!Repository.isValidRefName(refPattern)) { + throw new InvalidNameException(refPattern); + } + } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java index ce7530eb2c..1c70b04d0e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java @@ -33,7 +33,7 @@ import java.util.regex.Pattern; abstract class SectionMatcher { static SectionMatcher wrap(AccessSection section) { String ref = section.getName(); - if (AccessSection.isAccessSection(ref)) { + if (AccessSection.isValid(ref)) { return wrap(ref, section); } else { return null; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java index c0d2034cf6..40d4290a1a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java @@ -14,24 +14,18 @@ package com.google.gerrit.server.project; -import static com.google.gerrit.server.project.RefControl.isRE; -import static com.google.gerrit.server.project.RefControl.shortestExample; -import static com.google.gerrit.server.project.RefControl.toRegExp; - import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.server.cache.Cache; import com.google.gerrit.server.cache.CacheModule; +import com.google.gerrit.server.util.MostSpecificComparator; import com.google.inject.Inject; import com.google.inject.Module; import com.google.inject.Singleton; import com.google.inject.TypeLiteral; import com.google.inject.name.Named; -import org.apache.commons.lang.StringUtils; - import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.IdentityHashMap; import java.util.List; @@ -161,103 +155,5 @@ public class SectionSortCache { } } - /** - * Order the Ref Pattern by the most specific. This sort is done by: - *
    - *
  • 1 - The minor value of Levenshtein string distance between the branch - * name and the regex string shortest example. A shorter distance is a more - * specific match. - *
  • 2 - Finites first, infinities after. - *
  • 3 - Number of transitions. - *
  • 4 - Length of the expression text. - *
- * - * Levenshtein distance is a measure of the similarity between two strings. - * The distance is the number of deletions, insertions, or substitutions - * required to transform one string into another. - * - * For example, if given refs/heads/m* and refs/heads/*, the distances are 5 - * and 6. It means that refs/heads/m* is more specific because it's closer to - * refs/heads/master than refs/heads/*. - * - * Another example could be refs/heads/* and refs/heads/[a-zA-Z]*, the - * distances are both 6. Both are infinite, but refs/heads/[a-zA-Z]* has more - * transitions, which after all turns it more specific. - */ - private static final class MostSpecificComparator implements - Comparator { - private final String refName; - MostSpecificComparator(String refName) { - this.refName = refName; - } - - public int compare(AccessSection a, AccessSection b) { - return compare(a.getName(), b.getName()); - } - - private int compare(final String pattern1, final String pattern2) { - int cmp = distance(pattern1) - distance(pattern2); - if (cmp == 0) { - boolean p1_finite = finite(pattern1); - boolean p2_finite = finite(pattern2); - - if (p1_finite && !p2_finite) { - cmp = -1; - } else if (!p1_finite && p2_finite) { - cmp = 1; - } else /* if (f1 == f2) */{ - cmp = 0; - } - } - if (cmp == 0) { - cmp = transitions(pattern1) - transitions(pattern2); - } - if (cmp == 0) { - cmp = pattern2.length() - pattern1.length(); - } - return cmp; - } - - private int distance(String pattern) { - String example; - if (isRE(pattern)) { - example = shortestExample(pattern); - - } else if (pattern.endsWith("/*")) { - example = pattern.substring(0, pattern.length() - 1) + '1'; - - } else if (pattern.equals(refName)) { - return 0; - - } else { - return Math.max(pattern.length(), refName.length()); - } - return StringUtils.getLevenshteinDistance(example, refName); - } - - private boolean finite(String pattern) { - if (isRE(pattern)) { - return toRegExp(pattern).toAutomaton().isFinite(); - - } else if (pattern.endsWith("/*")) { - return false; - - } else { - return true; - } - } - - private int transitions(String pattern) { - if (isRE(pattern)) { - return toRegExp(pattern).toAutomaton().getNumberOfTransitions(); - - } else if (pattern.endsWith("/*")) { - return pattern.length(); - - } else { - return pattern.length(); - } - } - } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/MostSpecificComparator.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/MostSpecificComparator.java new file mode 100644 index 0000000000..804a7ec299 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/MostSpecificComparator.java @@ -0,0 +1,124 @@ +// Copyright (C) 2011 The Android Open Source Project +// +// 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.google.gerrit.server.util; + +import com.google.gerrit.common.data.RefConfigSection; +import com.google.gerrit.server.project.RefControl; + +import org.apache.commons.lang.StringUtils; + +import java.util.Comparator; + +/** + * Order the Ref Pattern by the most specific. This sort is done by: + *
    + *
  • 1 - The minor value of Levenshtein string distance between the branch + * name and the regex string shortest example. A shorter distance is a more + * specific match. + *
  • 2 - Finites first, infinities after. + *
  • 3 - Number of transitions. + *
  • 4 - Length of the expression text. + *
+ * + * Levenshtein distance is a measure of the similarity between two strings. + * The distance is the number of deletions, insertions, or substitutions + * required to transform one string into another. + * + * For example, if given refs/heads/m* and refs/heads/*, the distances are 5 + * and 6. It means that refs/heads/m* is more specific because it's closer to + * refs/heads/master than refs/heads/*. + * + * Another example could be refs/heads/* and refs/heads/[a-zA-Z]*, the + * distances are both 6. Both are infinite, but refs/heads/[a-zA-Z]* has more + * transitions, which after all turns it more specific. + */ +public final class MostSpecificComparator implements + Comparator { + private final String refName; + + public MostSpecificComparator(String refName) { + this.refName = refName; + } + + @Override + public int compare(RefConfigSection a, RefConfigSection b) { + return compare(a.getName(), b.getName()); + } + + public int compare(final String pattern1, final String pattern2) { + int cmp = distance(pattern1) - distance(pattern2); + if (cmp == 0) { + boolean p1_finite = finite(pattern1); + boolean p2_finite = finite(pattern2); + + if (p1_finite && !p2_finite) { + cmp = -1; + } else if (!p1_finite && p2_finite) { + cmp = 1; + } else /* if (f1 == f2) */{ + cmp = 0; + } + } + if (cmp == 0) { + cmp = transitions(pattern1) - transitions(pattern2); + } + if (cmp == 0) { + cmp = pattern2.length() - pattern1.length(); + } + return cmp; + } + + private int distance(String pattern) { + String example; + if (RefControl.isRE(pattern)) { + example = RefControl.shortestExample(pattern); + + } else if (pattern.endsWith("/*")) { + example = pattern.substring(0, pattern.length() - 1) + '1'; + + } else if (pattern.equals(refName)) { + return 0; + + } else { + return Math.max(pattern.length(), refName.length()); + } + return StringUtils.getLevenshteinDistance(example, refName); + } + + private boolean finite(String pattern) { + if (RefControl.isRE(pattern)) { + return RefControl.toRegExp(pattern).toAutomaton().isFinite(); + + } else if (pattern.endsWith("/*")) { + return false; + + } else { + return true; + } + } + + private int transitions(String pattern) { + if (RefControl.isRE(pattern)) { + return RefControl.toRegExp(pattern).toAutomaton() + .getNumberOfTransitions(); + + } else if (pattern.endsWith("/*")) { + return pattern.length(); + + } else { + return pattern.length(); + } + } +}